//--------------------------------------------------------------------------------------
// File: ImeUi.cpp
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//--------------------------------------------------------------------------------------
#include "dxut.h"
#include "ImeUi.h"
#include <math.h>
#include <msctf.h>
#include <malloc.h>
#include <strsafe.h>

// Ignore typecast warnings
#pragma warning( disable : 4312 ) 
#pragma warning( disable : 4244 ) 
#pragma warning( disable : 4311 ) 


#define MAX_CANDIDATE_LENGTH 256
#define COUNTOF(a) ( sizeof( a ) / sizeof( ( a )[0] ) )
#define POSITION_UNINITIALIZED	((DWORD)-1)

#define LANG_CHT	MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL)
#define LANG_CHS	MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED)

#define MAKEIMEVERSION(major,minor) ( (DWORD)( ( (BYTE)( major ) << 24 ) | ( (BYTE)( minor ) << 16 ) ) )
#define IMEID_VER(dwId)		( ( dwId ) & 0xffff0000 )
#define IMEID_LANG(dwId)	( ( dwId ) & 0x0000ffff )

#define _CHT_HKL_DAYI				( (HKL)0xE0060404 )	// DaYi
#define _CHT_HKL_NEW_PHONETIC		( (HKL)0xE0080404 )	// New Phonetic
#define _CHT_HKL_NEW_CHANG_JIE		( (HKL)0xE0090404 )	// New Chang Jie
#define _CHT_HKL_NEW_QUICK			( (HKL)0xE00A0404 )	// New Quick
#define _CHT_HKL_HK_CANTONESE		( (HKL)0xE00B0404 )	// Hong Kong Cantonese
#define _CHT_IMEFILENAME	"TINTLGNT.IME"	// New Phonetic
#define _CHT_IMEFILENAME2	"CINTLGNT.IME"	// New Chang Jie
#define _CHT_IMEFILENAME3	"MSTCIPHA.IME"	// Phonetic 5.1
#define IMEID_CHT_VER42 ( LANG_CHT | MAKEIMEVERSION( 4, 2 ) )	// New(Phonetic/ChanJie)IME98  : 4.2.x.x // Win98
#define IMEID_CHT_VER43 ( LANG_CHT | MAKEIMEVERSION( 4, 3 ) )	// New(Phonetic/ChanJie)IME98a : 4.3.x.x // Win2k
#define IMEID_CHT_VER44 ( LANG_CHT | MAKEIMEVERSION( 4, 4 ) )	// New ChanJie IME98b          : 4.4.x.x // WinXP
#define IMEID_CHT_VER50 ( LANG_CHT | MAKEIMEVERSION( 5, 0 ) )	// New(Phonetic/ChanJie)IME5.0 : 5.0.x.x // WinME
#define IMEID_CHT_VER51 ( LANG_CHT | MAKEIMEVERSION( 5, 1 ) )	// New(Phonetic/ChanJie)IME5.1 : 5.1.x.x // IME2002(w/OfficeXP)
#define IMEID_CHT_VER52 ( LANG_CHT | MAKEIMEVERSION( 5, 2 ) )	// New(Phonetic/ChanJie)IME5.2 : 5.2.x.x // IME2002a(w/WinXP)
#define IMEID_CHT_VER60 ( LANG_CHT | MAKEIMEVERSION( 6, 0 ) )	// New(Phonetic/ChanJie)IME6.0 : 6.0.x.x // New IME 6.0(web download)
#define IMEID_CHT_VER_VISTA ( LANG_CHT | MAKEIMEVERSION( 7, 0 ) )	// All TSF TIP under Cicero UI-less mode: a hack to make GetImeId() return non-zero value

#define _CHS_HKL		( (HKL)0xE00E0804 )	// MSPY
#define _CHS_IMEFILENAME	"PINTLGNT.IME"	// MSPY1.5/2/3
#define _CHS_IMEFILENAME2	"MSSCIPYA.IME"	// MSPY3 for OfficeXP
#define IMEID_CHS_VER41	( LANG_CHS | MAKEIMEVERSION( 4, 1 ) )	// MSPY1.5	// SCIME97 or MSPY1.5 (w/Win98, Office97)
#define IMEID_CHS_VER42	( LANG_CHS | MAKEIMEVERSION( 4, 2 ) )	// MSPY2	// Win2k/WinME
#define IMEID_CHS_VER53	( LANG_CHS | MAKEIMEVERSION( 5, 3 ) )	// MSPY3	// WinXP

static CHAR	signature[] = "%%%IMEUILIB:070111%%%";

static IMEUI_APPEARANCE gSkinIME = {
	0,			// symbolColor;
	0x404040,   // symbolColorOff;
	0xff000000,	// symbolColorText;
	24,			// symbolHeight;
	0xa0,		// symbolTranslucence;
	0,			// symbolPlacement;
	NULL,		// symbolFont;
	0xffffffff,	// candColorBase;
	0xff000000,	// candColorBorder;
	0,			// candColorText;
	0x00ffff00,	// compColorInput;
	0x000000ff,	// compColorTargetConv;
	0x0000ff00,	// compColorConverted;
	0x00ff0000,	// compColorTargetNotConv;
	0x00ff0000,	// compColorInputErr;
	0x80,		// compTranslucence;
	0,			// compColorText;
	2,			// caretWidth;
	1,			// caretYMargin;
};

struct _SkinCompStr
{
	DWORD	colorInput;
	DWORD	colorTargetConv;
	DWORD	colorConverted;
	DWORD	colorTargetNotConv;
	DWORD	colorInputErr;
};

_SkinCompStr gSkinCompStr;

// Definition from Win98DDK version of IMM.H
typedef struct tagINPUTCONTEXT2 {
    HWND                hWnd;                           
    BOOL                fOpen;                          
    POINT               ptStatusWndPos;                 
    POINT               ptSoftKbdPos;                   
    DWORD               fdwConversion;                  
    DWORD               fdwSentence;                    
    union   {                                           
        LOGFONTA        A;                              
        LOGFONTW        W;                              
    } lfFont;                                           
    COMPOSITIONFORM     cfCompForm;                     
    CANDIDATEFORM       cfCandForm[4];                  
    HIMCC               hCompStr;                       
    HIMCC               hCandInfo;                      
    HIMCC               hGuideLine;                     
    HIMCC               hPrivate;                       
    DWORD               dwNumMsgBuf;                    
    HIMCC               hMsgBuf;                        
    DWORD               fdwInit;                        
    DWORD               dwReserve[3];                   
} INPUTCONTEXT2, *PINPUTCONTEXT2, NEAR *NPINPUTCONTEXT2, FAR *LPINPUTCONTEXT2;  


// Class to disable Cicero in case ImmDisableTextFrameService() doesn't disable it completely
class CDisableCicero
{
public:
	CDisableCicero() : m_ptim( NULL ), m_bComInit( false )
	{
	}
	~CDisableCicero()
	{
		Uninitialize();
	}
	void Initialize()
	{
		if ( m_bComInit )
		{
			return;
		}
		HRESULT hr;
		hr = CoInitializeEx( NULL, COINIT_APARTMENTTHREADED );
	    if ( SUCCEEDED( hr ) )
		{
			m_bComInit = true;
			hr = CoCreateInstance( CLSID_TF_ThreadMgr,
				NULL,
				CLSCTX_INPROC_SERVER,
				__uuidof(ITfThreadMgr),
				(void**)&m_ptim );
		}
	}
	void Uninitialize()
	{
		if ( m_ptim )
		{
			m_ptim->Release();
			m_ptim = NULL;
		}
		if ( m_bComInit )
			CoUninitialize();
		m_bComInit = false;
	}

	void DisableCiceroOnThisWnd( HWND hwnd )
	{
		if ( m_ptim == NULL )
			return;
		ITfDocumentMgr* pdimPrev; // the dim that is associated previously.
		// Associate NULL dim to the window.
		// When this window gets the focus, Cicero does not work and IMM32 IME
		// will be activated.
		if ( SUCCEEDED( m_ptim->AssociateFocus( hwnd, NULL, &pdimPrev ) ) )
		{
			if ( pdimPrev )
				pdimPrev->Release();
		}
	}
private:
	ITfThreadMgr* m_ptim;
	bool m_bComInit;
};
static CDisableCicero g_disableCicero;

#define _IsLeadByte(x) ( LeadByteTable[(BYTE)( x )] )
static void _PumpMessage();
static BYTE LeadByteTable[256];
#define _ImmGetContext	ImmGetContext
#define _ImmReleaseContext	ImmReleaseContext
#define _ImmAssociateContext	ImmAssociateContext
static LONG (WINAPI* _ImmGetCompositionString)( HIMC himc, DWORD dwIndex, LPVOID lpBuf, DWORD dwBufLen );
#define _ImmGetOpenStatus	ImmGetOpenStatus
#define _ImmSetOpenStatus	ImmSetOpenStatus
#define _ImmGetConversionStatus	ImmGetConversionStatus
static DWORD (WINAPI* _ImmGetCandidateList)( HIMC himc, DWORD deIndex, LPCANDIDATELIST lpCandList, DWORD dwBufLen );
static LPINPUTCONTEXT2 (WINAPI* _ImmLockIMC)(HIMC hIMC);
static BOOL (WINAPI* _ImmUnlockIMC)(HIMC hIMC);
static LPVOID (WINAPI* _ImmLockIMCC)(HIMCC hIMCC);
static BOOL (WINAPI* _ImmUnlockIMCC)(HIMCC hIMCC);
#define _ImmGetDefaultIMEWnd	ImmGetDefaultIMEWnd
#define _ImmGetIMEFileNameA	ImmGetIMEFileNameA
#define _ImmGetVirtualKey	ImmGetVirtualKey
#define _ImmNotifyIME	ImmNotifyIME
#define _ImmSetConversionStatus	ImmSetConversionStatus
#define _ImmSimulateHotKey	ImmSimulateHotKey
#define _ImmIsIME	ImmIsIME

// private API provided by CHT IME. Available on version 6.0 or later.
UINT (WINAPI*_GetReadingString)(HIMC himc, UINT uReadingBufLen, LPWSTR lpwReadingBuf, PINT pnErrorIndex, BOOL* pfIsVertical, PUINT puMaxReadingLen);
BOOL (WINAPI*_ShowReadingWindow)(HIMC himc, BOOL bShow);

// Callbacks
void (CALLBACK *ImeUiCallback_DrawRect)( int x1, int y1, int x2, int y2, DWORD color );
void (CALLBACK *ImeUiCallback_DrawFans)(const IMEUI_VERTEX* paVertex, UINT uNum);
void* (__cdecl *ImeUiCallback_Malloc)( size_t bytes );
void (__cdecl *ImeUiCallback_Free)( void* ptr );
void (CALLBACK *ImeUiCallback_OnChar)( WCHAR wc );

static void (*_SendCompString)();
static LRESULT (WINAPI* _SendMessage)(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) = SendMessageA;
static DWORD (* _GetCandidateList)(HIMC himc, DWORD dwIndex, LPCANDIDATELIST* ppCandList);

static HWND g_hwndMain;
static HWND g_hwndCurr;
static HIMC g_himcOrg;
static bool g_bImeEnabled = false;
static TCHAR g_szCompositionString[256];
static BYTE  g_szCompAttrString[256];
static DWORD g_IMECursorBytes = 0;
static DWORD g_IMECursorChars = 0;
static TCHAR g_szCandidate[MAX_CANDLIST][MAX_CANDIDATE_LENGTH];
static DWORD g_dwSelection, g_dwCount;
static UINT g_uCandPageSize;
static DWORD g_bDisableImeCompletely = false;
static DWORD g_dwIMELevel;
static DWORD g_dwIMELevelSaved;
static TCHAR g_szMultiLineCompString[ 256 * ( 3 - sizeof(TCHAR) ) ];
static bool g_bReadingWindow = false;
static bool g_bHorizontalReading = false;
static bool g_bVerticalCand = true;
static UINT g_uCaretBlinkTime = 0;
static UINT g_uCaretBlinkLast = 0;
static bool g_bCaretDraw = false;
static bool g_bChineseIME;
static bool g_bInsertMode = true;
static TCHAR g_szReadingString[32];	// Used only in case of horizontal reading window
static int g_iReadingError;	// Used only in case of horizontal reading window
static UINT g_screenWidth, g_screenHeight;
static DWORD g_dwPrevFloat;
static bool bIsSendingKeyMessage = false;
static OSVERSIONINFOA g_osi;
static bool g_bInitialized = false;
static bool g_bCandList = false;
static DWORD g_dwCandX, g_dwCandY;
static DWORD g_dwCaretX, g_dwCaretY;
static DWORD g_hCompChar;
static int g_iCandListIndexBase;
static DWORD g_dwImeUiFlags = IMEUI_FLAG_SUPPORT_CARET;
static bool g_bUILessMode = false;
static HMODULE g_hImmDll = NULL;

#define IsNT() (g_osi.dwPlatformId == VER_PLATFORM_WIN32_NT)

struct CompStringAttribute
{
	UINT			caretX;
	UINT			caretY;
	CImeUiFont_Base* pFont;
	DWORD			colorComp;
	DWORD			colorCand;
	RECT			margins;
};

static CompStringAttribute g_CaretInfo;
static DWORD g_dwState = IMEUI_STATE_OFF;
static DWORD swirl = 0;
static double lastSwirl;

#define INDICATOR_NON_IME	0
#define INDICATOR_CHS		1
#define INDICATOR_CHT		2
#define INDICATOR_KOREAN	3
#define INDICATOR_JAPANESE	4

#define GETLANG()		LOWORD(g_hklCurrent)
#define GETPRIMLANG()	((WORD)PRIMARYLANGID(GETLANG()))
#define GETSUBLANG()	SUBLANGID(GETLANG())

#define LANG_CHS MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED)
#define LANG_CHT MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL)

static HKL g_hklCurrent = 0;
static UINT g_uCodePage = 0;
static LPTSTR g_aszIndicator[] =
{
		TEXT("A"),
#ifdef UNICODE
		L"\x7B80",
		L"\x7E41",
		L"\xac00",
		L"\x3042",
#else
		"\xd6\xd0",
		"\xa4\xa4",
		"\xb0\xa1",
		"\x82\xa0",
#endif
};
static LPTSTR g_pszIndicatior = g_aszIndicator[0];

static void GetReadingString(HWND hWnd);
static DWORD GetImeId( UINT uIndex = 0 );
static void CheckToggleState();
static void DrawImeIndicator();
static void DrawCandidateList();
static void DrawCompositionString( bool bDrawCompAttr );
static void GetReadingWindowOrientation( DWORD dwId );
static void OnInputLangChangeWorker();
static void OnInputLangChange();
static void SetImeApi();
static void CheckInputLocale();
static void SetSupportLevel( DWORD dwImeLevel );
void ImeUi_SetSupportLevel(DWORD dwImeLevel);


//
//	local helper functions
//
inline LRESULT SendKeyMsg(HWND hwnd, UINT msg, WPARAM wp)
{
	bIsSendingKeyMessage = true;
	LRESULT lRc = _SendMessage(hwnd, msg, wp, 1);
	bIsSendingKeyMessage = false;
	return lRc;
}
#define SendKeyMsg_DOWN(hwnd,vk)	SendKeyMsg(hwnd, WM_KEYDOWN, vk)
#define SendKeyMsg_UP(hwnd,vk)		SendKeyMsg(hwnd, WM_KEYUP, vk)

///////////////////////////////////////////////////////////////////////////////
//
//  CTsfUiLessMode
//      Handles IME events using Text Service Framework (TSF). Before Vista,
//      IMM (Input Method Manager) API has been used to handle IME events and
//      inqueries. Some IMM functions lose backward compatibility due to design
//      of TSF, so we have to use new TSF interfaces.
//
///////////////////////////////////////////////////////////////////////////////
class CTsfUiLessMode
{
protected:
	// Sink receives event notifications
	class CUIElementSink : public ITfUIElementSink, public ITfInputProcessorProfileActivationSink, public ITfCompartmentEventSink
	{
	public:
		CUIElementSink();
		~CUIElementSink();

		// IUnknown
		STDMETHODIMP QueryInterface(REFIID riid, void **ppvObj);
		STDMETHODIMP_(ULONG) AddRef(void);
		STDMETHODIMP_(ULONG) Release(void);

		// ITfUIElementSink
		//   Notifications for Reading Window events. We could process candidate as well, but we'll use IMM for simplicity sake.
		STDMETHODIMP BeginUIElement(DWORD dwUIElementId, BOOL *pbShow);
		STDMETHODIMP UpdateUIElement(DWORD dwUIElementId);
		STDMETHODIMP EndUIElement(DWORD dwUIElementId);

		// ITfInputProcessorProfileActivationSink
		//   Notification for keyboard input locale change
		STDMETHODIMP OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid,
			REFGUID guidProfile, HKL hkl, DWORD dwFlags);

		// ITfCompartmentEventSink
		//    Notification for open mode (toggle state) change
		STDMETHODIMP OnChange(REFGUID rguid);

	private:
		LONG _cRef;
	};

	static void MakeReadingInformationString(ITfReadingInformationUIElement* preading);
	static void MakeCandidateStrings(ITfCandidateListUIElement* pcandidate);
	static ITfUIElement* GetUIElement(DWORD dwUIElementId);
	static BOOL GetCompartments( ITfCompartmentMgr** ppcm, ITfCompartment** ppTfOpenMode, ITfCompartment** ppTfConvMode );
	static BOOL SetupCompartmentSinks( BOOL bResetOnly = FALSE, ITfCompartment* pTfOpenMode = NULL, ITfCompartment* ppTfConvMode = NULL );

	static ITfThreadMgrEx* m_tm;
	static DWORD m_dwUIElementSinkCookie;
	static DWORD m_dwAlpnSinkCookie;
	static DWORD m_dwOpenModeSinkCookie;
	static DWORD m_dwConvModeSinkCookie;
	static CUIElementSink *m_TsfSink;
	static int m_nCandidateRefCount;	// Some IME shows multiple candidate lists but the Library doesn't support multiple candidate list. 
										// So track open / close events to make sure the candidate list opened last is shown.
	CTsfUiLessMode() {}	// this class can't be instanciated

public:
	static BOOL SetupSinks();
	static void ReleaseSinks();
	static BOOL CurrentInputLocaleIsIme();
	static void UpdateImeState(BOOL bResetCompartmentEventSink = FALSE);
	static void EnableUiUpdates(bool bEnable);
};

ITfThreadMgrEx* CTsfUiLessMode::m_tm;
DWORD CTsfUiLessMode::m_dwUIElementSinkCookie = TF_INVALID_COOKIE;
DWORD CTsfUiLessMode::m_dwAlpnSinkCookie = TF_INVALID_COOKIE;
DWORD CTsfUiLessMode::m_dwOpenModeSinkCookie = TF_INVALID_COOKIE;
DWORD CTsfUiLessMode::m_dwConvModeSinkCookie = TF_INVALID_COOKIE;
CTsfUiLessMode::CUIElementSink* CTsfUiLessMode::m_TsfSink = NULL;
int CTsfUiLessMode::m_nCandidateRefCount = NULL;

static unsigned long _strtoul( LPCSTR psz, LPTSTR*, int )
{
	if ( !psz )
		return 0;

	ULONG ulRet = 0;
	if ( psz[0] == '0' && ( psz[1] == 'x' || psz[1] == 'X' ) )
	{
		psz += 2;
		ULONG ul = 0;
		while ( *psz )
		{
			if ( '0' <= *psz && *psz <= '9' )
				ul = *psz - '0';
			else if ( 'A' <= *psz && *psz <= 'F' )
				ul = *psz - 'A' + 10;
			else if ( 'a' <= *psz && *psz <= 'f' )
				ul = *psz - 'a' + 10;
			else
				break;
			ulRet = ulRet * 16 + ul;
			psz++;
		}
	}
	else {
		while ( *psz && ( '0' <= *psz && *psz <= '9' ) )
		{
			ulRet = ulRet * 10 + ( *psz - '0' );
			psz++;
		}
	}
	return ulRet;
}

#ifdef UNICODE
#define GetCharCount(psz) lstrlen(psz)
#define GetCharCountFromBytes(psz,iBytes) (iBytes)
static void AW_SendCompString()
{
	int i, iLen;
	if ( ImeUiCallback_OnChar )
	{
		for ( i = 0; g_szCompositionString[i]; i++ )
		{
			ImeUiCallback_OnChar( g_szCompositionString[i] );
		}
		return;
	}

	BYTE szCompStr[COUNTOF(g_szCompositionString) * 2];
	iLen = WideCharToMultiByte(g_uCodePage, 0, g_szCompositionString, -1, 
				(LPSTR)szCompStr, COUNTOF(szCompStr), NULL, NULL) - 1; // don't need to send NUL terminator;
	for (i = 0; i < iLen; i++)
	{
		SendKeyMsg(g_hwndCurr, WM_CHAR, szCompStr[i]);
	}
}

// The following AW_Imm* functions are there to support Win95/98 first version.
// They can be deleted if the game doesn't supports them (i.e. games requires Win98 SE or later).
static DWORD AW_GetCandidateList(HIMC himc, DWORD dwIndex, LPCANDIDATELIST* ppCandList)
{
	DWORD dwBufLen = ImmGetCandidateListA( himc, dwIndex, NULL, 0 );
	if (dwBufLen)
	{
		LPCANDIDATELIST pCandList = (LPCANDIDATELIST)ImeUiCallback_Malloc(dwBufLen);
		if (pCandList) {
			dwBufLen = ImmGetCandidateListA( himc, dwIndex, pCandList, dwBufLen );
			if (dwBufLen) {
				int i;
				int wideBufLen = 0;
				for (i = 0; i < (int)pCandList->dwCount; i++) {
					wideBufLen += MultiByteToWideChar(g_uCodePage, 0, (LPSTR)pCandList + pCandList->dwOffset[i], -1, NULL, 0) * sizeof(WCHAR);
				}
				wideBufLen += pCandList->dwOffset[0];
				*ppCandList = (LPCANDIDATELIST)ImeUiCallback_Malloc(wideBufLen);
				LPCANDIDATELIST pCandListW = *ppCandList;
				memcpy(pCandListW, pCandList, pCandList->dwOffset[0]);
				LPWSTR pwz = (LPWSTR)((LPSTR)pCandListW + pCandList->dwOffset[0]);
				for (i = 0; i < (int)pCandList->dwCount; i++) {
					pCandListW->dwOffset[i] = (LPSTR)pwz - (LPSTR)pCandListW;
					pwz += MultiByteToWideChar(g_uCodePage, 0, (LPSTR)pCandList + pCandList->dwOffset[i], -1, pwz, 256);
				}
				dwBufLen = wideBufLen;
			}
			ImeUiCallback_Free(pCandList);
		}
	}
	return dwBufLen;
}

static LONG WINAPI AW_ImmGetCompositionString(HIMC himc, DWORD dwIndex, LPVOID lpBuf, DWORD dwBufLen)
{
	char pszMb[COUNTOF(g_szCompositionString) * 2];
	DWORD dwRet = ImmGetCompositionStringA(himc, dwIndex, pszMb, sizeof(pszMb));
	switch (dwIndex) {
	case GCS_RESULTSTR:
	case GCS_COMPSTR:
		if (dwRet) {
			pszMb[dwRet] = 0;
			dwRet = (DWORD)MultiByteToWideChar(g_uCodePage, 0, pszMb, -1, (LPWSTR)lpBuf, dwBufLen);
			if (dwRet) {
				// Note that ImmGetCompositionString() returns number of bytes copied, regardless of the width of character.
				dwRet = (dwRet - 1) * sizeof(WCHAR);
			}
		}
		break;
	case GCS_CURSORPOS:
		dwRet /= 2;
		break;
	case GCS_COMPATTR: {
		char pszMb2[COUNTOF(g_szCompositionString) * 2];
		DWORD dwRet2 = ImmGetCompositionStringA(himc, GCS_COMPSTR, pszMb2, sizeof(pszMb2));
		if (!dwRet2) {
			dwRet2 = ImmGetCompositionStringA(himc, GCS_RESULTSTR, pszMb2, sizeof(pszMb2));
			if (!dwRet2) {
				return 0;
			}
		}
		char* pOut = (char*)lpBuf;
		for (DWORD i = 0; i < dwRet; i++) {
			*pOut++ = pszMb[i];	// copy attribute
			if (_IsLeadByte(pszMb2[i]))
				i++;
		}
		dwRet = pOut - (char*)lpBuf;
		}
		break;
	}
	return dwRet;
}

#else	// !UNICODE
// returns number of characters from number of bytes
static int GetCharCountFromBytes(LPCSTR pszString, int iBytes)
{
	int iCount = 0;
	int i;
	for (i = 0; pszString[i] && i < iBytes; i++) {
		iCount++;
		if (_IsLeadByte(pszString[i]))
			i++;
	}
	if (i != iBytes)
		iCount = -iCount;	// indicate error - iBytes specifies wrong boundary (i.e. the last byte is leadbyte)
	return iCount;
}

static int GetCharCount(LPTSTR psz)
{
	int i = 0;
	while (*psz) {
		if (_IsLeadByte(*psz)) {
			psz++;
		}
		psz++;
		i++;
	}
	return i;
}

static DWORD WA_GetCandidateList(HIMC himc, DWORD dwIndex, LPCANDIDATELIST* ppCandList)
{
	DWORD dwBufLen = ImmGetCandidateListW( himc, dwIndex, NULL, 0 );
	if (dwBufLen)
	{
		LPCANDIDATELIST pCandList = (LPCANDIDATELIST)ImeUiCallback_Malloc(dwBufLen);
		if (pCandList)
		{
			dwBufLen = ImmGetCandidateListW( himc, dwIndex, pCandList, dwBufLen );
			if (dwBufLen)
			{
				int i;
				int mbBufLen = 0;
				for ( i = 0; i < (int)pCandList->dwCount; i++ )
				{
					mbBufLen += WideCharToMultiByte( g_uCodePage, 0, (LPWSTR)((LPSTR)pCandList + pCandList->dwOffset[i]), -1, NULL, 0, NULL, NULL );
				}
				mbBufLen += pCandList->dwOffset[0];
				*ppCandList = (LPCANDIDATELIST)ImeUiCallback_Malloc(mbBufLen);
				LPCANDIDATELIST pCandListA = *ppCandList;
				memcpy(pCandListA, pCandList, pCandList->dwOffset[0]);
				LPSTR psz = (LPSTR)pCandListA + pCandList->dwOffset[0];
				for (i = 0; i < (int)pCandList->dwCount; i++)
				{
					pCandListA->dwOffset[i] = (LPSTR)psz - (LPSTR)pCandListA;
					psz += WideCharToMultiByte( g_uCodePage, 0, (LPWSTR)((LPSTR)pCandList + pCandList->dwOffset[i]), -1, psz, 256, NULL, NULL );
				}
				dwBufLen = mbBufLen;
			}
			ImeUiCallback_Free(pCandList);
		}
	}
	return dwBufLen;
}

static LONG WINAPI WA_ImmGetCompositionString(HIMC himc, DWORD dwIndex, LPVOID lpBuf, DWORD dwBufLen)
{
	WCHAR pwzUc[COUNTOF(g_szCompositionString)];
	DWORD dwRet = ImmGetCompositionStringW(himc, dwIndex, pwzUc, sizeof(pwzUc));
	switch (dwIndex) {
	case GCS_RESULTSTR:
	case GCS_COMPSTR:
		if (dwRet) {
			pwzUc[dwRet / sizeof(WCHAR)] = 0;
			dwRet = (DWORD)WideCharToMultiByte( g_uCodePage, 0, pwzUc, -1, (LPSTR)lpBuf, dwBufLen, NULL, NULL );
			if (dwRet) {
				dwRet = dwRet - 1;
			}
		}
		break;

	case GCS_CURSORPOS:
		{
		WCHAR pwzUc2[COUNTOF(g_szCompositionString)];
		DWORD dwRet2 = ImmGetCompositionStringW(himc, GCS_COMPSTR, pwzUc2, sizeof(pwzUc2));
		if (!dwRet2)
		{
			dwRet2 = ImmGetCompositionStringW(himc, GCS_RESULTSTR, pwzUc2, sizeof(pwzUc2));
			if (!dwRet2)
			{
				return 0;
			}
		}
		dwRet2 /= 2;
		//The return value of WideCharToMultiByte() should probably be checked/asserted for success.
		//bounds violation (overflow) 'pszMb[iRc]'
		const int bufSize = COUNTOF(g_szCompositionString) * 2;
		char pszMb[bufSize];
		int iRc = WideCharToMultiByte( g_uCodePage, 0, pwzUc2, dwRet2, pszMb, sizeof( pszMb ), NULL, NULL );
		assert( iRc > 0 ); //WideCharToMultiByte returns 0 if it failed, and it should *never* be negative in the first place
		if (iRc >= bufSize) //if we wrote more bytes than the length of the buffer, we need to terminate it
		{
			pszMb[ bufSize - 1] = 0; //0 terminate the end of the buffer
		}
		else
		{
			pszMb[ iRc ] = 0;
		}
		char* psz = pszMb;
		for ( dwRet2 = 0; dwRet2 != dwRet; dwRet2++ )
		{
			if ( _IsLeadByte( *psz ) )
				psz++;
			psz++;
		}
		dwRet = psz - pszMb;
		}
		break;

	case GCS_COMPATTR: {
		WCHAR pwzUc2[COUNTOF(g_szCompositionString)];
		DWORD dwRet2 = ImmGetCompositionStringW(himc, GCS_COMPSTR, pwzUc2, sizeof(pwzUc2));
		if (!dwRet2)
		{
			dwRet2 = ImmGetCompositionStringW(himc, GCS_RESULTSTR, pwzUc2, sizeof(pwzUc2));
			if (!dwRet2)
			{
				return 0;
			}
		}
		dwRet2 /= 2;
		const int bufSize = COUNTOF(g_szCompositionString) * 2;
		char pszMb[bufSize];
		int iRc = WideCharToMultiByte( g_uCodePage, 0, pwzUc2, dwRet2, pszMb, sizeof( pszMb ), NULL, NULL );
		assert( iRc > 0 ); //WideCharToMultiByte returns 0 if it failed, and it should *never* be negative in the first place
		if (iRc >= bufSize) //if we wrote more bytes than the length of the buffer, we need to terminate it
		{
			pszMb[ bufSize - 1] = 0; //0 terminate the end of the buffer
		}
		else
		{
			pszMb[ iRc ] = 0;
		}
		char* pSrc = (char*)pwzUc;
		char* pOut = (char*)lpBuf;
		for (char* psz = pszMb; *psz; psz++, pSrc++)
		{
			*pOut++ = *pSrc;	// copy attribute
			if ( _IsLeadByte( *psz ) )
			{
				*pOut++ = *pSrc;
				psz++;
			}
			// buffer overrun protection, pOut is incremented in the loop, but not part	of the 
			// loop	invariant test.	To make	the	code more readable we have a test rather than
			// rolling this	into the for stmt.
			if ((DWORD)(pOut-(char*)lpBuf) >=dwBufLen)
				break;
		}
		dwRet = pOut - (char*)lpBuf;
		}
		break;
	}
	return dwRet;
}

#endif	// UNICODE

static void ComposeCandidateLine( int index, LPCTSTR pszCandidate )
{
	LPTSTR psz = g_szCandidate[index];
	*psz++ = (TCHAR)( TEXT( '0' ) + ( ( index + g_iCandListIndexBase ) % 10 ) );
	if ( g_bVerticalCand )
	{
		*psz++ = TEXT( ' ' );
	}
	while ( *pszCandidate && (COUNTOF(g_szCandidate[index]) > (psz-g_szCandidate[index])) )
	{
		*psz++ = *pszCandidate++;
	}
	*psz = 0;
}

static void SendCompString()
{
	int i, iLen = lstrlen(g_szCompositionString);
	if ( ImeUiCallback_OnChar )
	{
		LPCWSTR pwz;
#ifdef UNICODE
		pwz = g_szCompositionString;
#else
		WCHAR szUnicode[ COUNTOF( g_szCompositionString ) ];
		pwz = szUnicode;
		iLen = MultiByteToWideChar( g_uCodePage, 0, g_szCompositionString, -1, szUnicode, COUNTOF(szUnicode) ) - 1;
#endif
		for ( i = 0; i < iLen; i++ )
		{
			ImeUiCallback_OnChar( pwz[i] );
		}
		return;
	}
	for ( i = 0; i < iLen; i++ )
	{
		SendKeyMsg(g_hwndCurr, WM_CHAR,
#ifdef UNICODE
			(WPARAM)g_szCompositionString[i]
#else
			(WPARAM)(BYTE)g_szCompositionString[i]
#endif
			);
	}
}

static DWORD GetCandidateList(HIMC himc, DWORD dwIndex, LPCANDIDATELIST* ppCandList)
{
	DWORD dwBufLen = _ImmGetCandidateList( himc, dwIndex, NULL, 0 );
	if ( dwBufLen )
	{
		*ppCandList = (LPCANDIDATELIST)ImeUiCallback_Malloc(dwBufLen);
		dwBufLen = _ImmGetCandidateList( himc, dwIndex, *ppCandList, dwBufLen );
	}
	return dwBufLen;
}

static void SendControlKeys(UINT vk, UINT num)
{
	if (num == 0)
		return;
	for (UINT i = 0; i < num ; i++) {
		SendKeyMsg_DOWN(g_hwndCurr, vk);
	}
	SendKeyMsg_UP(g_hwndCurr, vk);
}

// send key messages to erase composition string.
static void CancelCompString(HWND hwnd, bool bUseBackSpace = true, int iNewStrLen = 0)
{
	if (g_dwIMELevel != 3)
		return;
	int cc = GetCharCount(g_szCompositionString);
	int i;
	// move caret to the end of composition string
	SendControlKeys(VK_RIGHT, cc - g_IMECursorChars);

	if (bUseBackSpace || g_bInsertMode)
		iNewStrLen = 0;

	// The caller sets bUseBackSpace to false if there's possibility of sending 
	// new composition string to the app right after this function call.
	// 
	// If the app is in overwriting mode and new comp string is 
	// shorter than current one, delete previous comp string 
	// till it's same long as the new one. Then move caret to the beginning of comp string.
	// New comp string will overwrite old one.
	if (iNewStrLen < cc)
	{
		for (i = 0; i < cc - iNewStrLen; i++)
		{
			SendKeyMsg_DOWN(hwnd, VK_BACK);
			SendKeyMsg(hwnd, WM_CHAR, 8);	//Backspace character
		}
		SendKeyMsg_UP(hwnd, VK_BACK);
	}
	else
		iNewStrLen = cc;

	SendControlKeys(VK_LEFT, iNewStrLen);
}

// initialize composition string data.
static void InitCompStringData(void)
{
	g_IMECursorBytes = 0;
	g_IMECursorChars = 0;
	memset(&g_szCompositionString, 0, sizeof(g_szCompositionString));
	memset(&g_szCompAttrString, 0, sizeof(g_szCompAttrString));
}

static void DrawCaret(DWORD x, DWORD y, DWORD height)
{
	if (g_bCaretDraw && ImeUiCallback_DrawRect)
		ImeUiCallback_DrawRect(x, y + gSkinIME.caretYMargin, x + gSkinIME.caretWidth, y + height - gSkinIME.caretYMargin, g_CaretInfo.colorComp);
}

//
// Apps that draw the composition string on top of composition string attribute
// in level 3 support should call this function twice in rendering a frame.
//     // Draw edit box UI;
//     ImeUi_RenderUI(true, false);	// paint composition string attribute;
//     // Draw text in the edit box;
//     ImeUi_RenderUi(false, true); // paint the rest of IME UI;
//
void ImeUi_RenderUI(bool bDrawCompAttr, bool bDrawOtherUi)
{
	if (!g_bInitialized || !g_bImeEnabled || !g_CaretInfo.pFont)
		return;
	if (!bDrawCompAttr && !bDrawOtherUi)
		return;	// error case
	if (g_dwIMELevel == 2) {
		if (!bDrawOtherUi)
			return;	// 1st call for level 3 support
	}

	if (bDrawOtherUi)
		DrawImeIndicator();

	DrawCompositionString( bDrawCompAttr );

	if ( bDrawOtherUi )
		DrawCandidateList();
}

static void DrawImeIndicator()
{
	bool bOn = g_dwState != IMEUI_STATE_OFF;

	IMEUI_VERTEX PieData[17];
	float SizeOfPie = (float) gSkinIME.symbolHeight;

	memset( PieData, 0, sizeof( PieData ) );

	switch(gSkinIME.symbolPlacement)
	{
	case 0: // vertical centering IME indicator
		{
			if (SizeOfPie + g_CaretInfo.margins.right+4 > g_screenWidth)
			{
				PieData[0].sx=(-SizeOfPie/2) + g_CaretInfo.margins.left-4;
				PieData[0].sy= (float) g_CaretInfo.margins.top + (g_CaretInfo.margins.bottom - g_CaretInfo.margins.top)/2;
			}
			else
			{
				PieData[0].sx=-(SizeOfPie/2) + g_CaretInfo.margins.right+gSkinIME.symbolHeight+4;
				PieData[0].sy= (float) g_CaretInfo.margins.top + (g_CaretInfo.margins.bottom - g_CaretInfo.margins.top)/2;
			}
			break;
		}
	case 1: // upperleft
		PieData[0].sx = 4+ (SizeOfPie/2);
		PieData[0].sy = 4+ (SizeOfPie/2);
		break;
	case 2: // upperright
		PieData[0].sx = g_screenWidth - (4+ (SizeOfPie/2));
		PieData[0].sy = 4+ (SizeOfPie/2);
		break;
	case 3: // lowerright
		PieData[0].sx = g_screenWidth - (4+ (SizeOfPie/2));
		PieData[0].sy = g_screenHeight - (4+ (SizeOfPie/2));
		break;
	case 4: // lowerleft
		PieData[0].sx = 4+ (SizeOfPie/2);
		PieData[0].sy = g_screenHeight - (4+ (SizeOfPie/2));
		break;
	}
	PieData[0].rhw=1.0f;
	if (bOn)
	{
		if ( GetTickCount() - lastSwirl > 250 )
		{
			swirl++;
			lastSwirl = GetTickCount();
			if (swirl > 13)
				swirl = 0;
		}
	}
	else
		swirl = 0;
	for( int t1 = 1; t1 < 16; t1++ )
	{
		float radian = 2.0f * 3.1415926f * ( t1 - 1 + ( bOn * swirl ) ) / 14.0f;
		PieData[t1].sx = (float)( PieData[0].sx + SizeOfPie / 2 * cos( radian ) );
		PieData[t1].sy = (float)( PieData[0].sy + SizeOfPie / 2 * sin( radian ) );
		PieData[t1].rhw = 1.0f;
	}

	PieData[0].color=0xffffff +  (((DWORD)gSkinIME.symbolTranslucence)<<24);
	if ( !gSkinIME.symbolColor && bOn )
	{
		{
	 		PieData[1].color=0xff0000 + (((DWORD)gSkinIME.symbolTranslucence)<<24);
		 	PieData[2].color=0xff3000 + (((DWORD)gSkinIME.symbolTranslucence)<<24);
		 	PieData[3].color=0xff6000 + (((DWORD)gSkinIME.symbolTranslucence)<<24);
	 		PieData[4].color=0xff9000 + (((DWORD)gSkinIME.symbolTranslucence)<<24);
		 	PieData[5].color=0xffC000 + (((DWORD)gSkinIME.symbolTranslucence)<<24);
		 	PieData[6].color=0xffff00 + (((DWORD)gSkinIME.symbolTranslucence)<<24);
	 		PieData[7].color=0xC0ff00 + (((DWORD)gSkinIME.symbolTranslucence)<<24);
		 	PieData[8].color=0x90ff00 + (((DWORD)gSkinIME.symbolTranslucence)<<24);
		 	PieData[9].color=0x60ff00 + (((DWORD)gSkinIME.symbolTranslucence)<<24);
	 		PieData[10].color=0x30c0ff + (((DWORD)gSkinIME.symbolTranslucence)<<24);
		 	PieData[11].color=0x00a0ff + (((DWORD)gSkinIME.symbolTranslucence)<<24);
		 	PieData[12].color=0x3090ff + (((DWORD)gSkinIME.symbolTranslucence)<<24);
	 		PieData[13].color=0x6060ff + (((DWORD)gSkinIME.symbolTranslucence)<<24);
		 	PieData[14].color=0x9030ff + (((DWORD)gSkinIME.symbolTranslucence)<<24);
		 	PieData[15].color=0xc000ff + (((DWORD)gSkinIME.symbolTranslucence)<<24);
		}
	}
	else
	{
		DWORD dwColor = bOn ? gSkinIME.symbolColor : gSkinIME.symbolColorOff;
		for( int t1=1; t1<16; t1++ )
		{
			PieData[t1].color= dwColor + (((DWORD)gSkinIME.symbolTranslucence)<<24);
		}
	}
	PieData[16] = PieData[1];

    if( ImeUiCallback_DrawFans )
	    ImeUiCallback_DrawFans( PieData, 17 );

	float fHeight = gSkinIME.symbolHeight*0.625f;

	// fix for Ent Gen #120 - reduce the height of character when Korean IME is on
	if ( GETPRIMLANG() == LANG_KOREAN && bOn )
	{
		fHeight *= 0.8f;
	}

	if (gSkinIME.symbolFont)
	{
#ifdef DS2
		// save the font height here since DS2 shares editbox font and indicator font
		DWORD _w, _h;
		g_CaretInfo.pFont->GetTextExtent( TEXT(" "), &_w, &_h );
#endif //DS2

		// GOS deals height in points that is 1/72nd inch and assumes display device is 96dpi.
		fHeight = fHeight * 96 / 72;
		gSkinIME.symbolFont->SetHeight((UINT)fHeight);
		gSkinIME.symbolFont->SetColor((((DWORD)gSkinIME.symbolTranslucence)<<24) | gSkinIME.symbolColorText);

		//
		// draw the proper symbol over the fan
		//
		DWORD  w, h;
		LPCTSTR cszSymbol = ( g_dwState == IMEUI_STATE_ON ) ? g_pszIndicatior : g_aszIndicator[0];

		gSkinIME.symbolFont->GetTextExtent(cszSymbol, &w, &h);
		gSkinIME.symbolFont->SetPosition((int)(PieData[0].sx)-w/2, (int)(PieData[0].sy)-h/2);
		gSkinIME.symbolFont->DrawText( cszSymbol );

#ifdef DS2
		// revert the height.
		g_CaretInfo.pFont->SetHeight( _h );
		
		// Double-check: Confirm match by testing a range of font heights to find best fit
		DWORD _h2;
		g_CaretInfo.pFont->GetTextExtent( TEXT(" "), &_w, &_h2 );
		if ( _h2 < _h )
		{
			for ( int i=1; _h2<_h && i<10; i++ )
			{
				g_CaretInfo.pFont->SetHeight( _h+i );
				g_CaretInfo.pFont->GetTextExtent( TEXT(" "), &_w, &_h2 );
			}
		}
		else if ( _h2 > _h )
		{
			for ( int i=1; _h2>_h && i<10; i++ )
			{
				g_CaretInfo.pFont->SetHeight( _h-i );
				g_CaretInfo.pFont->GetTextExtent( TEXT(" "), &_w, &_h2 );
			}
		}
#endif //DS2
	}
}

static void DrawCompositionString( bool bDrawCompAttr )
{
	// Process timer for caret blink
	UINT uCurrentTime = GetTickCount();
	if (uCurrentTime - g_uCaretBlinkLast > g_uCaretBlinkTime)
	{
		g_uCaretBlinkLast = uCurrentTime;
		g_bCaretDraw = !g_bCaretDraw;
	}

	int i = 0;

	g_CaretInfo.pFont->SetColor(g_CaretInfo.colorComp);

	DWORD uDummy;

	int len = lstrlen(g_szCompositionString);

	DWORD bgX = g_CaretInfo.caretX;
	DWORD bgY = g_CaretInfo.caretY;
	g_dwCaretX = POSITION_UNINITIALIZED;
	g_dwCaretY = POSITION_UNINITIALIZED;
	DWORD candX = POSITION_UNINITIALIZED;
	DWORD candY = 0;
	LPTSTR pszMlcs = g_szMultiLineCompString;

	DWORD wCompChar = 0;
	DWORD hCompChar = 0;
	g_CaretInfo.pFont->GetTextExtent( TEXT(" "), &uDummy, &hCompChar );
	if (g_dwIMELevel == 3 && g_IMECursorBytes && g_szCompositionString[0])
	{
		// shift starting point of drawing composition string according to the current caret position.
		TCHAR temp = g_szCompositionString[g_IMECursorBytes];
		g_szCompositionString[g_IMECursorBytes] = 0;
		g_CaretInfo.pFont->GetTextExtent(g_szCompositionString, &wCompChar, &hCompChar);
		g_szCompositionString[g_IMECursorBytes] = temp;
		bgX -= wCompChar;
	}

	//
	// Draw the background colors for IME text nuggets
	//
	bool saveCandPos = false;
	DWORD cType = 1;
	LPTSTR pszCurrentCompLine = g_szCompositionString;
	DWORD dwCompLineStart = bgX;
	DWORD bgXnext = bgX;

	if (GETPRIMLANG() != LANG_KOREAN || g_bCaretDraw)	// Korean uses composition attribute as blinking block caret
    for (i = 0; i < len ; i += cType)
	{
		DWORD bgColor = 0x00000000;
		TCHAR szChar[3];
		szChar[0] = g_szCompositionString[i];
		szChar[1] = szChar[2] = 0;
#ifndef UNICODE
		cType = 1 + ((_IsLeadByte(g_szCompositionString[i])) ? 1 : 0);
		if (cType == 2 && g_szCompositionString[i+1])	// in case we have 0 in trailbyte, we don't count it.
			szChar[1] = g_szCompositionString[i+1];
#endif
		bgX = bgXnext;
		TCHAR cSave = g_szCompositionString[i + cType];
		g_szCompositionString[i + cType] = 0;
		g_CaretInfo.pFont->GetTextExtent( pszCurrentCompLine, &bgXnext, &hCompChar );
		g_szCompositionString[i + cType] = cSave;
		bgXnext += dwCompLineStart;
		wCompChar = bgXnext - bgX;

		switch(g_szCompAttrString[i])
		{
		case ATTR_INPUT:
			bgColor = gSkinCompStr.colorInput;
			break;
		case ATTR_TARGET_CONVERTED:
			bgColor = gSkinCompStr.colorTargetConv;
			if ( IMEID_LANG( GetImeId() ) != LANG_CHS )
				saveCandPos = true;
			break;						  
		case ATTR_CONVERTED:
			bgColor = gSkinCompStr.colorConverted;
			break;
		case ATTR_TARGET_NOTCONVERTED:
			//
			// This is the one the user is working with currently
			//
			bgColor = gSkinCompStr.colorTargetNotConv;
			break;
		case ATTR_INPUT_ERROR:
			bgColor = gSkinCompStr.colorInputErr;
			break;
		default:
			// STOP( TEXT( "Attributes on IME characters are wrong" ) );
			break;
		}

		if (g_dwIMELevel == 3 && bDrawCompAttr)
		{
			if ((LONG)bgX >= g_CaretInfo.margins.left && (LONG)bgX <= g_CaretInfo.margins.right)
			{
				if ( g_dwImeUiFlags & IMEUI_FLAG_SUPPORT_CARET )
				{
				    if( ImeUiCallback_DrawRect )
					    ImeUiCallback_DrawRect(bgX, bgY, bgX + wCompChar, bgY + hCompChar, bgColor);
				}
				else
				{
				    if( ImeUiCallback_DrawRect )
    					ImeUiCallback_DrawRect(bgX - wCompChar, bgY, bgX, bgY + hCompChar, bgColor);
				}
			}
		}
		else if (g_dwIMELevel == 2)
		{
			// make sure enough buffer space (possible space, NUL for current line, possible DBCS, 2 more NUL) 
			// are available in multiline composition string buffer
			bool bWrite = ( pszMlcs - g_szMultiLineCompString <
					COUNTOF( g_szMultiLineCompString ) - 5 * ( 3 - sizeof(TCHAR) ) );

			if ((LONG)(bgX + wCompChar) >= g_CaretInfo.margins.right) {
				bgX = dwCompLineStart = bgXnext = g_CaretInfo.margins.left;
				bgY = bgY + hCompChar;
				pszCurrentCompLine = g_szCompositionString + i;
				if (bWrite)
				{
					if (pszMlcs == g_szMultiLineCompString || pszMlcs[-1] == 0)
						*pszMlcs++ = ' ';	// to avoid zero length line
					*pszMlcs++ = 0;
				}
			}
			if( ImeUiCallback_DrawRect )
    			ImeUiCallback_DrawRect(bgX, bgY, bgX + wCompChar, bgY + hCompChar, bgColor);
			if (bWrite) {
				*pszMlcs++ = g_szCompositionString[i];
#ifndef UNICODE
				if (cType == 2)
					*pszMlcs++ = g_szCompositionString[i+1];
#endif
			}
			if ((DWORD)i == g_IMECursorBytes)
			{
				g_dwCaretX = bgX;
				g_dwCaretY = bgY;
			}
		}
		if ( ( saveCandPos && candX == POSITION_UNINITIALIZED ) ||
			 ( IMEID_LANG( GetImeId() ) == LANG_CHS && i / ( 3 - sizeof(TCHAR) ) == (int)g_IMECursorChars ) )
		{
			candX = bgX;
			candY = bgY;
		}
		saveCandPos = false;
	}

	bgX = bgXnext;
	if (g_dwIMELevel == 2)
	{
		// in case the caret in composition string is at the end of it, draw it here
		if (len != 0 && (DWORD)i == g_IMECursorBytes)
		{
				g_dwCaretX = bgX;
				g_dwCaretY = bgY;
		}

		// Draw composition string.
		//assert(pszMlcs - g_szMultiLineCompString <=
		//			sizeof(g_szMultiLineCompString) / sizeof(g_szMultiLineCompString[0]) - 2);
		*pszMlcs++ = 0;
		*pszMlcs++ = 0;
		DWORD x, y;
		x = g_CaretInfo.caretX;
		y = g_CaretInfo.caretY;
		pszMlcs = g_szMultiLineCompString;
		while (*pszMlcs && 
			pszMlcs - g_szMultiLineCompString < sizeof(g_szMultiLineCompString) / sizeof(g_szMultiLineCompString[0]))
		{
			g_CaretInfo.pFont->SetPosition(x, y);
			g_CaretInfo.pFont->DrawText(pszMlcs);
			pszMlcs += lstrlen( pszMlcs ) + 1;
			x = g_CaretInfo.margins.left;
			y += hCompChar;
		}
	}
	// for changing z-order of caret
	if ( g_dwCaretX != POSITION_UNINITIALIZED && g_dwCaretY != POSITION_UNINITIALIZED )
	{
		DrawCaret(g_dwCaretX, g_dwCaretY, hCompChar);
	}
    g_dwCandX = candX;
    g_dwCandY = candY;
	g_hCompChar = hCompChar;
}

static void DrawCandidateList()
{
    DWORD candX = g_dwCandX;
    DWORD candY = g_dwCandY;
	DWORD hCompChar = g_hCompChar;
	int i;

	// draw candidate list / reading window
	if ( !g_dwCount || g_szCandidate[0][0] == 0 )
	{
		return;
	}

	// If position of candidate list is not initialized yet, set it here.
	if (candX == POSITION_UNINITIALIZED)
	{
		// CHT IME in Vista doesn't have ATTR_TARGET_CONVERTED attribute while typing, 
		// so display the candidate list near the caret in the composition string
		if (GETLANG() == LANG_CHT && GetImeId() != 0 && g_dwCaretX != POSITION_UNINITIALIZED)
		{
			candX = g_dwCaretX;
			candY = g_dwCaretY;
		}
		else
		{
			candX = g_CaretInfo.caretX;
			candY = g_CaretInfo.caretY;
		}
	}

	SIZE largest = {0,0};

	static DWORD uDigitWidth = 0;
	DWORD uSpaceWidth = 0;
	static DWORD uDigitWidthList[10];
	static CImeUiFont_Base* pPrevFont = NULL;
	// find out the widest width of the digits
	if ( pPrevFont != g_CaretInfo.pFont )
	{
		pPrevFont = g_CaretInfo.pFont;
		for(int cnt=0; cnt<=9; cnt++)
		{
			DWORD uDW = 0;
			DWORD uDH = 0;
			TCHAR ss[8];
            swprintf_s(ss, COUNTOF(ss), TEXT("%d"), cnt);
			g_CaretInfo.pFont->GetTextExtent( ss, &uDW, &uDH );
			uDigitWidthList[cnt] = uDW;
			if ( uDW > uDigitWidth )
				uDigitWidth = uDW;
			if ( (signed)uDH > largest.cy)
				largest.cy = uDH;
		}
	}
	uSpaceWidth = uDigitWidth;
	DWORD dwMarginX = (uSpaceWidth + 1) / 2;
	DWORD adwCandWidth[ MAX_CANDLIST ];

	// Find out the widest width of the candidate strings
	DWORD dwCandWidth = 0;
	if (g_bReadingWindow && g_bHorizontalReading)
		g_CaretInfo.pFont->GetTextExtent(g_szReadingString, (DWORD*)&largest.cx, (DWORD*)&largest.cy);
	else
	{
		for (i = 0; g_szCandidate[i][0] && i < (int)g_uCandPageSize; i++)
		{
			DWORD tx = 0;
			DWORD ty = 0;

			if (g_bReadingWindow)
				g_CaretInfo.pFont->GetTextExtent(g_szCandidate[i], &tx, &ty);
			else {
				if (g_bVerticalCand)
					g_CaretInfo.pFont->GetTextExtent(g_szCandidate[i]+2, &tx, &ty);
				else
					g_CaretInfo.pFont->GetTextExtent(g_szCandidate[i]+1, &tx, &ty);
				tx = tx + uDigitWidth + uSpaceWidth;
			}

			if ((signed)tx > largest.cx)
				largest.cx = tx;
			if ((signed)ty > largest.cy)
				largest.cy = ty;
			adwCandWidth[ i ] = tx;
			dwCandWidth += tx;
		}
	}

	DWORD slotsUsed;
	if (g_bReadingWindow && g_dwCount < g_uCandPageSize)
		slotsUsed = g_dwCount;
	else
		slotsUsed = g_uCandPageSize;

	// Show candidate list above composition string if there isn't enough room below.
	DWORD dwCandHeight;
	if (g_bVerticalCand && !(g_bReadingWindow && g_bHorizontalReading))
		dwCandHeight = slotsUsed * largest.cy + 2;
	else
		dwCandHeight = largest.cy + 2;
	if ( candY + hCompChar + dwCandHeight > g_screenHeight)
		candY -= dwCandHeight;
	else
		candY += hCompChar;
	if ( (int)candY < 0 )
		candY = 0;

	// Move candidate list horizontally to keep it inside of screen
	if ( !g_bReadingWindow && IMEID_LANG( GetImeId() ) == LANG_CHS )
		dwCandWidth += dwMarginX * (slotsUsed-1);
	else if ( g_bReadingWindow && g_bHorizontalReading )
		dwCandWidth = largest.cx + 2 + dwMarginX * 2;
	else if ( g_bVerticalCand || g_bReadingWindow )
		dwCandWidth = largest.cx + 2 + dwMarginX * 2;
	else
		dwCandWidth = slotsUsed * (largest.cx + 1) + 1;
	if (candX + dwCandWidth > g_screenWidth)
		candX = g_screenWidth - dwCandWidth;
	if ( (int)candX < 0 )
		candX = 0;

	// Draw frame and background of candidate list / reading window
	int seperateLineX = 0;
	int left = candX;
	int top = candY;
	int right = candX + dwCandWidth;
	int bottom = candY + dwCandHeight;
	if( ImeUiCallback_DrawRect )
	    ImeUiCallback_DrawRect(left, top, right, bottom, gSkinIME.candColorBorder);
	left++;
	top++;
	right--;
	bottom--;
	if ( g_bReadingWindow || IMEID_LANG( GetImeId() ) == LANG_CHS )
	{
	    if( ImeUiCallback_DrawRect )
    		ImeUiCallback_DrawRect(left,top, right, bottom, gSkinIME.candColorBase);
	}
	else if ( g_bVerticalCand )
	{
		// uDigitWidth is the max width of all digits. 
		if ( !g_bReadingWindow )
		{
			seperateLineX = left + dwMarginX + uDigitWidth + uSpaceWidth / 2;
    	    if( ImeUiCallback_DrawRect )
            {
    			ImeUiCallback_DrawRect(left, top, seperateLineX-1, bottom, gSkinIME.candColorBase);
			    ImeUiCallback_DrawRect(seperateLineX, top, right, bottom, gSkinIME.candColorBase);
			}
		}
	}
	else
	{
		for (i = 0; (DWORD)i < slotsUsed; i++)
		{
		    if( ImeUiCallback_DrawRect )
    			ImeUiCallback_DrawRect(left, top, left + largest.cx, bottom, gSkinIME.candColorBase);
			left += largest.cx + 1;
		}
	}

	// Draw candidates / reading strings
	candX++;
	candY++;
	if (g_bReadingWindow && g_bHorizontalReading)
	{
		int iStart = -1, iEnd = -1, iDummy;
		candX += dwMarginX;

		// draw background of error character if it exists
		TCHAR szTemp[ COUNTOF( g_szReadingString ) ];
		if (g_iReadingError >= 0) {
			wcscpy_s(szTemp, COUNTOF(szTemp), g_szReadingString);
			LPTSTR psz = szTemp + g_iReadingError;
#ifdef UNICODE
			psz++;
#else
			psz += ( _IsLeadByte( szTemp[g_iReadingError] ) ) ? 2 : 1;
#endif
			*psz = 0;
			g_CaretInfo.pFont->GetTextExtent(szTemp, (DWORD*)&iEnd, (DWORD*)&iDummy);
			TCHAR cSave = szTemp[ g_iReadingError ];
			szTemp[g_iReadingError] = 0;
			g_CaretInfo.pFont->GetTextExtent(szTemp, (DWORD*)&iStart, (DWORD*)&iDummy);
			szTemp[g_iReadingError] = cSave;
		    if( ImeUiCallback_DrawRect )
    			ImeUiCallback_DrawRect(candX + iStart, candY, candX + iEnd, candY + largest.cy, gSkinIME.candColorBorder);
		}

		g_CaretInfo.pFont->SetPosition(candX , candY);
		g_CaretInfo.pFont->SetColor(g_CaretInfo.colorCand);
		g_CaretInfo.pFont->DrawText(g_szReadingString);

		// draw error character if it exists
		if (iStart >= 0) {
			g_CaretInfo.pFont->SetPosition(candX + iStart, candY);
			if ( gSkinIME.candColorBase != 0xffffffff || gSkinIME.candColorBorder != 0xff000000 )
				g_CaretInfo.pFont->SetColor(g_CaretInfo.colorCand);
			else
				g_CaretInfo.pFont->SetColor(0xff000000 + (~((0x00ffffff) & g_CaretInfo.colorCand)));
			g_CaretInfo.pFont->DrawText(szTemp + g_iReadingError);
		}
	}
	else
	{
		for (i = 0; i < (int)g_uCandPageSize && (DWORD)i < g_dwCount; i++)
		{
			if (g_dwSelection == (DWORD)i)
			{
				if ( gSkinIME.candColorBase != 0xffffffff || gSkinIME.candColorBorder != 0xff000000 )
					g_CaretInfo.pFont->SetColor(g_CaretInfo.colorCand);
				else
					g_CaretInfo.pFont->SetColor(0xff000000 + (~((0x00ffffff) & g_CaretInfo.colorCand)));

    		    if( ImeUiCallback_DrawRect )
		        {
    				if ( g_bReadingWindow || g_bVerticalCand )
    					ImeUiCallback_DrawRect( candX, candY + i * largest.cy,
    							candX - 1 + dwCandWidth, candY + (i + 1) * largest.cy, gSkinIME.candColorBorder );
    				else
    					ImeUiCallback_DrawRect( candX, candY,
    							candX + adwCandWidth[i], candY + largest.cy, gSkinIME.candColorBorder );
				}
			}
			else
				g_CaretInfo.pFont->SetColor(g_CaretInfo.colorCand);
			if (g_szCandidate[i][0] != 0)
			{
				if (!g_bReadingWindow && g_bVerticalCand)
				{
					TCHAR szOneDigit[2] = { g_szCandidate[i][0], 0 };
					int nOneDigit = g_szCandidate[i][0] - TEXT('0');
					TCHAR *szCandidateBody = g_szCandidate[i] + 2;

					int dx = candX + (seperateLineX - candX - uDigitWidthList[nOneDigit]) / 2;
					int dy = candY + largest.cy * i;
					
					g_CaretInfo.pFont->SetPosition( dx, dy );
					g_CaretInfo.pFont->DrawText(szOneDigit);
					g_CaretInfo.pFont->SetPosition( seperateLineX+dwMarginX, dy );
					g_CaretInfo.pFont->DrawText(szCandidateBody);
				}
				else if ( g_bReadingWindow )
				{
					g_CaretInfo.pFont->SetPosition( dwMarginX + candX, candY + i * largest.cy );
					g_CaretInfo.pFont->DrawText(g_szCandidate[i]);
				}
				else
				{
					g_CaretInfo.pFont->SetPosition( uSpaceWidth / 2 + candX, candY );
					g_CaretInfo.pFont->DrawText(g_szCandidate[i]);
				}
			}
			if ( !g_bReadingWindow && !g_bVerticalCand )
			{
				if ( IMEID_LANG( GetImeId() ) == LANG_CHS )
					candX += adwCandWidth[i] + dwMarginX;
				else
					candX += largest.cx + 1;
			}
		}
	}
}

static void CloseCandidateList()
{
	g_bCandList = false;
	if (!g_bReadingWindow)	// fix for Ent Gen #120.
	{
		g_dwCount = 0;
		memset(&g_szCandidate, 0, sizeof(g_szCandidate));
	}
}

//
//	ProcessIMEMessages()
//	Processes IME related messages and acquire information
//
LPARAM ImeUi_ProcessMessage( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM& lParam, bool * trapped )
{
	HIMC himc;
	int len;
	static LPARAM lAlt = 0x80000000, lCtrl = 0x80000000, lShift = 0x80000000;

	*trapped = false;
	if (!g_bInitialized || g_bDisableImeCompletely) 
	{
		return 0;
	}

	switch( uMsg )
	{
//
//	IME Handling
//
	case WM_INPUTLANGCHANGE:
		OnInputLangChange();
		break;

    case WM_IME_SETCONTEXT:
		//
		// We don't want anything to display, so we have to clear lParam and pass it to DefWindowProc().
		// Expecially important in Vista to receive IMN_CHANGECANDIDATE correctly.
		//
        lParam = 0;
        break;

    case WM_IME_STARTCOMPOSITION:
		InitCompStringData();
		*trapped = true;
		break;

    case WM_IME_COMPOSITION:
		{
			LONG lRet;
			TCHAR szCompStr[COUNTOF(g_szCompositionString)];

			*trapped = true;
			if (NULL == (himc = _ImmGetContext(hWnd)))
			{
				break;
			}

				// ResultStr must be processed before composition string.
		        if ( lParam & GCS_RESULTSTR )
				{
					lRet = (LONG)_ImmGetCompositionString( himc, GCS_RESULTSTR, szCompStr, COUNTOF( szCompStr ) ) / sizeof(TCHAR);
					szCompStr[lRet] = 0;
					CancelCompString( g_hwndCurr, false, GetCharCount( szCompStr ) );
					wcscpy_s(g_szCompositionString, COUNTOF(g_szCompositionString), szCompStr);
					_SendCompString();
					InitCompStringData();
				}
		        //
		        // Reads in the composition string.
		        //
		        if ( lParam & GCS_COMPSTR )
		        {
					//////////////////////////////////////////////////////
					// Retrieve the latest user-selected IME candidates
					lRet = (LONG)_ImmGetCompositionString( himc, GCS_COMPSTR, szCompStr, COUNTOF( szCompStr ) ) / sizeof(TCHAR);
					szCompStr[lRet] = 0;
					//
					// Remove the whole of the string
					//
					CancelCompString(g_hwndCurr, false, GetCharCount( szCompStr ) );

					wcscpy_s(g_szCompositionString, COUNTOF(g_szCompositionString), szCompStr);
					lRet = _ImmGetCompositionString( himc, GCS_COMPATTR, g_szCompAttrString, COUNTOF( g_szCompAttrString ) );
					g_szCompAttrString[lRet] = 0;
					// Older CHT IME uses composition string for reading string
					if ( GETLANG() == LANG_CHT && !GetImeId() )
					{
						int i, chars = lstrlen(g_szCompositionString) / (3 - sizeof(TCHAR));
						if (chars)
						{
							g_dwCount = 4;
							g_dwSelection = (DWORD)-1;	// don't select any candidate

							for (i = 3; i >= 0; i--)
							{
								if (i > chars - 1)
									g_szCandidate[i][0] = 0;
								else
								{
#ifdef UNICODE
									g_szCandidate[i][0] = g_szCompositionString[i];
									g_szCandidate[i][1] = 0;
#else
									g_szCandidate[i][0] = g_szCompositionString[i*2];
									g_szCandidate[i][1] = g_szCompositionString[i*2+1];
									g_szCandidate[i][2] = 0;
#endif
								}
							}
							g_uCandPageSize = MAX_CANDLIST;
							memset(g_szCompositionString, 0, 8);
							g_bReadingWindow = true;
							GetReadingWindowOrientation( 0 );
							if (g_bHorizontalReading) {
								g_iReadingError = -1;
								g_szReadingString[0] = 0;
								for (i = 0; i < (int)g_dwCount; i++) {
									if (g_dwSelection == (DWORD)i)
										g_iReadingError = lstrlen(g_szReadingString);
									LPCTSTR pszTmp = g_szCandidate[i];
				                    wcscat_s(g_szReadingString, COUNTOF(g_szReadingString), pszTmp);
								}
							}
						}
						else
							g_dwCount = 0;
					}

					// get caret position in composition string
					g_IMECursorBytes = _ImmGetCompositionString(himc, GCS_CURSORPOS, NULL, 0);
					g_IMECursorChars = GetCharCountFromBytes(g_szCompositionString, g_IMECursorBytes);

					if (g_dwIMELevel == 3)
					{
						// send composition string via WM_CHAR
						_SendCompString();
						// move caret to appropreate location
						len = GetCharCount(g_szCompositionString + g_IMECursorBytes);
						SendControlKeys(VK_LEFT, len);
					}
				}
				_ImmReleaseContext(hWnd, himc);
		}
		break;

    case WM_IME_ENDCOMPOSITION:
		CancelCompString(g_hwndCurr);
		InitCompStringData();
        break;

    case WM_IME_NOTIFY:
        switch (wParam)
        {
			case IMN_SETCONVERSIONMODE:
				{
					// Disable CHT IME software keyboard.
					static bool bNoReentrance = false;
					if (LANG_CHT == GETLANG() && !bNoReentrance)
					{
						bNoReentrance = true;
						DWORD dwConvMode, dwSentMode;
						_ImmGetConversionStatus( g_himcOrg, &dwConvMode, &dwSentMode );
						const DWORD dwFlag = IME_CMODE_SOFTKBD | IME_CMODE_SYMBOL;
						if ( dwConvMode & dwFlag )
							_ImmSetConversionStatus( g_himcOrg, dwConvMode & ~dwFlag, dwSentMode );
					}
					bNoReentrance = false;
				}
				// fall through
			case IMN_SETOPENSTATUS:
				if (g_bUILessMode)
					break;
				CheckToggleState();
				break;
			
			case IMN_OPENCANDIDATE:
			case IMN_CHANGECANDIDATE:
				if ( g_bUILessMode )
				{
					break;
				}
				{
					g_bCandList = true;
					*trapped = true;
					if (NULL == (himc = _ImmGetContext(hWnd)))
						break;
					
					LPCANDIDATELIST lpCandList;
					DWORD dwIndex, dwBufLen;

					g_bReadingWindow = false;
					dwIndex = 0;
					dwBufLen = _GetCandidateList( himc, dwIndex, &lpCandList );

					if ( dwBufLen )
					{
					    g_dwSelection = lpCandList->dwSelection;
					    g_dwCount = lpCandList->dwCount;

						int startOfPage = 0;
						if (GETLANG() == LANG_CHS && GetImeId())
						{
							// MSPY (CHS IME) has variable number of candidates in candidate window
							// find where current page starts, and the size of current page
							const int maxCandChar = 18 * (3 - sizeof(TCHAR));
							UINT cChars = 0;
							UINT i;
							for (i = 0; i < g_dwCount; i++)
							{
								UINT uLen = lstrlen(
									(LPTSTR)((DWORD)lpCandList + lpCandList->dwOffset[i])) + (3 - sizeof(TCHAR));
								if (uLen + cChars > maxCandChar)
								{
									if (i > g_dwSelection)
									{
										break;
									}
									startOfPage = i;
									cChars = uLen;
								}
								else
								{
									cChars += uLen;
								}
							}
							g_uCandPageSize = i - startOfPage;
						}
						else
						{
							g_uCandPageSize = min( lpCandList->dwPageSize, MAX_CANDLIST );
							startOfPage = g_bUILessMode ? lpCandList->dwPageStart : ( g_dwSelection / g_uCandPageSize ) * g_uCandPageSize;
						}

						g_dwSelection = ( GETLANG() == LANG_CHS && !GetImeId() ) ? (DWORD)-1
							: g_dwSelection - startOfPage;

					    memset(&g_szCandidate, 0, sizeof(g_szCandidate));
					    for (UINT i = startOfPage, j = 0;
							(DWORD)i < lpCandList->dwCount && j < g_uCandPageSize;
							i++, j++)
					    {
							ComposeCandidateLine( j,
								(LPTSTR)( (DWORD)lpCandList + lpCandList->dwOffset[i] ) );
					    }
					    ImeUiCallback_Free( (HANDLE)lpCandList );
						_ImmReleaseContext(hWnd, himc);

						// don't display selection in candidate list in case of Korean and old Chinese IME.
						if ( GETPRIMLANG() == LANG_KOREAN ||
							GETLANG() == LANG_CHT && !GetImeId() )
							g_dwSelection = (DWORD)-1;
					}
					break;
				}
			
			case IMN_CLOSECANDIDATE:
				if ( g_bUILessMode )
				{
					break;
				}
				CloseCandidateList();
				*trapped = true;
			    break;

			// Jun.16,2000 05:21 by yutaka.
			case IMN_PRIVATE:
				{
					if ( !g_bCandList )
					{
						GetReadingString( hWnd );
					}
					// Trap some messages to hide reading window
					DWORD dwId = GetImeId();
					switch (dwId) {
					case IMEID_CHT_VER42:
					case IMEID_CHT_VER43:
					case IMEID_CHT_VER44:
					case IMEID_CHS_VER41:
					case IMEID_CHS_VER42:
						if ((lParam == 1) || (lParam == 2))
						{
							*trapped = true;
						}
						break;
					case IMEID_CHT_VER50:
					case IMEID_CHT_VER51:
					case IMEID_CHT_VER52:
					case IMEID_CHT_VER60:
					case IMEID_CHS_VER53:
						if ((lParam == 16) || (lParam == 17) || (lParam == 26) || (lParam == 27) || (lParam == 28))
						{
							*trapped = true;
						}
						break;
					}
				}
				break;

			default:
				*trapped = true;
				break;
		}
		break;

	// fix for #15386 - When Text Service Framework is installed in Win2K, Alt+Shift and Ctrl+Shift combination (to switch 
	// input locale / keyboard layout) doesn't send WM_KEYUP message for the key that is released first. We need to check
	// if these keys are actually up whenever we receive key up message for other keys.
	case WM_KEYUP:
	case WM_SYSKEYUP:
		if ( !( lAlt & 0x80000000 ) && wParam != VK_MENU && ( GetAsyncKeyState( VK_MENU ) & 0x8000 ) == 0 )
		{
			PostMessageA( GetFocus(), WM_KEYUP, (WPARAM)VK_MENU, ( lAlt & 0x01ff0000 ) | 0xC0000001 );
		}	
		else if ( !( lCtrl & 0x80000000 ) && wParam != VK_CONTROL && ( GetAsyncKeyState( VK_CONTROL ) & 0x8000 ) == 0 )
		{
			PostMessageA( GetFocus(), WM_KEYUP, (WPARAM)VK_CONTROL, ( lCtrl & 0x01ff0000 ) | 0xC0000001 );
		}
		else if ( !( lShift & 0x80000000 ) && wParam != VK_SHIFT && ( GetAsyncKeyState( VK_SHIFT ) & 0x8000 ) == 0 )
		{
			PostMessageA( GetFocus(), WM_KEYUP, (WPARAM)VK_SHIFT, ( lShift & 0x01ff0000 ) | 0xC0000001 );
		}
		// fall through WM_KEYDOWN / WM_SYSKEYDOWN
	case WM_KEYDOWN:
	case WM_SYSKEYDOWN:
		{
			switch ( wParam )
			{
			case VK_MENU:
				lAlt = lParam;
				break;
			case VK_SHIFT:
				lShift = lParam;
				break;
			case VK_CONTROL:
				lCtrl = lParam;
				break;
			}
		}
		break;
	}
	return 0;
}

void ImeUi_SetCaretPosition(UINT x, UINT y)
{
	if (!g_bInitialized)
		return;
	g_CaretInfo.caretX = x;
	g_CaretInfo.caretY = y;
}

void ImeUi_SetCompStringAppearance( CImeUiFont_Base* pFont, DWORD color, const RECT* prc )
{
	if (!g_bInitialized)
		return;
	g_CaretInfo.pFont = pFont;
	g_CaretInfo.margins = *prc;

	if (0 == gSkinIME.candColorText)
		g_CaretInfo.colorCand = color;
	else
		g_CaretInfo.colorCand = gSkinIME.candColorText;
	if (0 == gSkinIME.compColorText)
		g_CaretInfo.colorComp = color;
	else
		g_CaretInfo.colorComp = gSkinIME.compColorText;
}

void ImeUi_SetState( DWORD dwState )
{
	if (!g_bInitialized)
		return;
	HIMC himc;
	if ( dwState == IMEUI_STATE_ON )
	{
		ImeUi_EnableIme( true );
	}
	if (NULL != (himc = _ImmGetContext(g_hwndCurr))) 
	{
		if (g_bDisableImeCompletely)
			dwState = IMEUI_STATE_OFF;

		bool bOn = dwState == IMEUI_STATE_ON;	// for non-Chinese IME
		switch ( GETPRIMLANG() ) {
		case LANG_CHINESE:
		{
		 	// toggle Chinese IME
			DWORD dwId;
			DWORD dwConvMode = 0, dwSentMode = 0;
			if ( ( g_bChineseIME && dwState == IMEUI_STATE_OFF ) || ( !g_bChineseIME && dwState != IMEUI_STATE_OFF ) )
			{
				_ImmSimulateHotKey( g_hwndCurr, IME_THOTKEY_IME_NONIME_TOGGLE );
				_PumpMessage();
			}
			if ( dwState != IMEUI_STATE_OFF )
			{
				dwId = GetImeId();
				if ( dwId )
				{
					_ImmGetConversionStatus( himc, &dwConvMode, &dwSentMode );
					dwConvMode = ( dwState == IMEUI_STATE_ON )
						? ( dwConvMode | IME_CMODE_NATIVE )
						: ( dwConvMode & ~IME_CMODE_NATIVE );
					_ImmSetConversionStatus( himc, dwConvMode, dwSentMode );
				}
			}
			break;
		}
		case LANG_KOREAN:
		 	// toggle Korean IME
			if ( ( bOn && g_dwState != IMEUI_STATE_ON ) || ( !bOn && g_dwState == IMEUI_STATE_ON ) )
			{
				_ImmSimulateHotKey(g_hwndCurr, IME_KHOTKEY_ENGLISH);
			}
			break;
		case LANG_JAPANESE:
			_ImmSetOpenStatus(himc, bOn);
			break;
		}
		_ImmReleaseContext(g_hwndCurr, himc);
		CheckToggleState();
	}
}

DWORD ImeUi_GetState()
{
	if (!g_bInitialized)
		return IMEUI_STATE_OFF;
	CheckToggleState();
	return g_dwState;
}

void ImeUi_EnableIme( bool bEnable )
{
	if (!g_bInitialized || !g_hwndCurr)
		return;
	if (g_bDisableImeCompletely)
		bEnable = false;

	if (g_hwndCurr == g_hwndMain)
	{
		HIMC himcDbg;
		himcDbg = _ImmAssociateContext( g_hwndCurr, bEnable? g_himcOrg : NULL );
	}
	g_bImeEnabled = bEnable;
	if ( bEnable )
	{
		CheckToggleState();
	}
	CTsfUiLessMode::EnableUiUpdates(bEnable);
}

bool ImeUi_IsEnabled( void )
{
	return g_bImeEnabled;
}

bool ImeUi_Initialize( HWND hwnd, bool bDisable )
{
	if ( g_bInitialized )
	{
		return true;
	}
	g_hwndMain = hwnd;
	g_disableCicero.Initialize();

	g_osi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
	GetVersionExA(&g_osi);

	bool bUnicodeImm = false;
	// IMM in NT or Win98 supports Unicode
	if ( g_osi.dwPlatformId == VER_PLATFORM_WIN32_NT ||
		( g_osi.dwMajorVersion > 4 ) ||
		( g_osi.dwMajorVersion == 4 ) && ( g_osi.dwMinorVersion > 0 ) ) {
		bUnicodeImm = true;
	}

	g_hImmDll = LoadLibraryA( "imm32.dll" );
	g_bDisableImeCompletely = false;

	if ( g_hImmDll )
	{
		_ImmLockIMC =               (LPINPUTCONTEXT2 (WINAPI*)(HIMC hIMC)) GetProcAddress(g_hImmDll, "ImmLockIMC");
		_ImmUnlockIMC =             (BOOL (WINAPI*)(HIMC hIMC)) GetProcAddress(g_hImmDll, "ImmUnlockIMC");
		_ImmLockIMCC =              (LPVOID (WINAPI*)(HIMCC hIMCC)) GetProcAddress(g_hImmDll, "ImmLockIMCC");
		_ImmUnlockIMCC =            (BOOL (WINAPI*)(HIMCC hIMCC)) GetProcAddress(g_hImmDll, "ImmUnlockIMCC");
		BOOL (WINAPI* _ImmDisableTextFrameService)(DWORD) = (BOOL (WINAPI*)(DWORD))GetProcAddress(g_hImmDll, "ImmDisableTextFrameService");
		if ( _ImmDisableTextFrameService )
		{
			_ImmDisableTextFrameService( (DWORD)-1 );
		}
	}
	else
	{
		g_bDisableImeCompletely = true;
		return false;
	}
#ifdef UNICODE
	if ( bUnicodeImm )
	{
		_ImmGetCompositionString =	ImmGetCompositionStringW;
		_ImmGetCandidateList =		ImmGetCandidateListW;
		_GetCandidateList =			GetCandidateList;
	}
	else
	{
		_ImmGetCandidateList =		ImmGetCandidateListA;
		_ImmGetCompositionString =	AW_ImmGetCompositionString;
		_GetCandidateList =			AW_GetCandidateList;
	}
#else
	if ( bUnicodeImm )
	{
		_ImmGetCompositionString =	WA_ImmGetCompositionString;
		_ImmGetCandidateList =		ImmGetCandidateListA;
		_GetCandidateList =			WA_GetCandidateList;
	}
	else
	{
		_ImmGetCompositionString =	ImmGetCompositionStringA;
		_ImmGetCandidateList =		ImmGetCandidateListA;
		_GetCandidateList =			GetCandidateList;
	}
#endif

	// There are the following combinations of code config, window type, and the method of sending characters.
	// Wnd: Unicode, Code: Unicode, Method: SendMessageW (SendMessageW must be supported since RegisterClassW is successful)
	// Wnd: non Uni, Code: Unicode, Method: AW_SendCompString (Send characters in multibyte after W->A conversion)
	// Wnd: Unicode, Code: non Uni, Method: SendMessageA (System does A->W conversion) - possible, but unlikely to be used.
	// Wnd: non Uni, Code: non Uni, Method: SendMessageA
#ifdef UNICODE
	if ( !IsWindowUnicode( hwnd ) )
	{
		_SendCompString = AW_SendCompString;
	}
	else
#endif
	{
		_SendCompString = SendCompString;
#ifdef UNICODE
		_SendMessage = SendMessageW;
#endif
	}

	// turn init flag on so that subsequent calls to ImeUi functions work. 
	g_bInitialized = true;

	ImeUi_SetWindow( g_hwndMain );
	g_himcOrg = _ImmGetContext( g_hwndMain );
	_ImmReleaseContext( g_hwndMain, g_himcOrg );

	if ( !g_himcOrg )
	{
		bDisable = true;
	}

	// the following pointers to function has to be initialized before this function is called.
	if ( bDisable ||
		!ImeUiCallback_Malloc ||
		!ImeUiCallback_Free
		)
	{
		g_bDisableImeCompletely = true;
		ImeUi_EnableIme(false);
		g_bInitialized = bDisable;
		return false;
	}

	g_uCaretBlinkTime = GetCaretBlinkTime();

#ifndef UNICODE
	// Check if system is SBCS system
	CPINFO cpi;
	BOOL bRc = GetCPInfo(CP_ACP, &cpi);
	if (bRc) {
		if (cpi.MaxCharSize == 1)
		{
			g_bDisableImeCompletely = true;	// SBCS system. Disable IME.
		}
	}
#endif

	g_CaretInfo.caretX = 0;
	g_CaretInfo.caretY = 0;
	g_CaretInfo.pFont = 0;
	g_CaretInfo.colorComp = 0;
	g_CaretInfo.colorCand = 0;
	g_CaretInfo.margins.left = 0;
	g_CaretInfo.margins.right = 640;
	g_CaretInfo.margins.top = 0;
	g_CaretInfo.margins.bottom = 480;

	CheckInputLocale();
	OnInputLangChangeWorker();
	ImeUi_SetSupportLevel(2);

	// SetupTSFSinks has to be called before CheckToggleState to make it work correctly.
	g_bUILessMode = CTsfUiLessMode::SetupSinks() != FALSE;
	CheckToggleState();
	if ( g_bUILessMode )
	{
		g_bChineseIME = ( GETPRIMLANG() == LANG_CHINESE ) && CTsfUiLessMode::CurrentInputLocaleIsIme();
		CTsfUiLessMode::UpdateImeState();
	}
	ImeUi_EnableIme(false);

	return true;
}

void ImeUi_Uninitialize()
{
	if ( !g_bInitialized )
	{
		return;
	}
	CTsfUiLessMode::ReleaseSinks();
	if ( g_hwndMain )
	{
		ImmAssociateContext(g_hwndMain, g_himcOrg);
	}
	g_hwndMain = NULL;
	g_himcOrg = NULL;
	if ( g_hImmDll )
	{
		FreeLibrary(g_hImmDll);
		g_hImmDll = NULL;
	}
	g_disableCicero.Uninitialize();
	g_bInitialized = false;
}

//
//	GetImeId( UINT uIndex )
//		returns 
//	returned value:
//	0: In the following cases
//		- Non Chinese IME input locale
//		- Older Chinese IME
//		- Other error cases
//
//	Othewise:
//      When uIndex is 0 (default)
//			bit 31-24:	Major version
//			bit 23-16:	Minor version
//			bit 15-0:	Language ID
//		When uIndex is 1
//			pVerFixedInfo->dwFileVersionLS
//
//	Use IMEID_VER and IMEID_LANG macro to extract version and language information.
//	
static DWORD GetImeId( UINT uIndex )
{
	static HKL hklPrev = 0;
	static DWORD dwRet[2] = {0, 0};
	
	DWORD 	dwVerSize;
	DWORD 	dwVerHandle;
	LPVOID	lpVerBuffer;
	LPVOID	lpVerData;
	UINT	cbVerData;
	char szTmp[1024];

	if ( uIndex >= sizeof( dwRet ) / sizeof( dwRet[0] ) )
		return 0;

	HKL kl = g_hklCurrent;
	if (hklPrev == kl)
	{
		return dwRet[uIndex];
	}
	hklPrev = kl;
	DWORD dwLang = ((DWORD)kl & 0xffff);

	if ( g_bUILessMode && GETLANG() == LANG_CHT )
	{
		// In case of Vista, artifitial value is returned so that it's not considered as older IME.
		dwRet[0] = IMEID_CHT_VER_VISTA;
		dwRet[1] = 0;
		return dwRet[0];
	}

	if ( kl != _CHT_HKL_NEW_PHONETIC && kl != _CHT_HKL_NEW_CHANG_JIE
		&& kl != _CHT_HKL_NEW_QUICK && kl != _CHT_HKL_HK_CANTONESE && kl != _CHS_HKL )
	{
		goto error;
	}
	
	if ( _ImmGetIMEFileNameA( kl, szTmp, sizeof( szTmp ) - 1 ) <= 0 )
	{
		goto error;
	}

	if ( !_GetReadingString )	// IME that doesn't implement private API
	{
#define LCID_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
		if ( ( CompareStringA( LCID_INVARIANT, NORM_IGNORECASE, szTmp, -1, _CHT_IMEFILENAME, -1 ) != 2 )
			&& ( CompareStringA( LCID_INVARIANT, NORM_IGNORECASE, szTmp, -1, _CHT_IMEFILENAME2, -1 ) != 2 )
			&& ( CompareStringA( LCID_INVARIANT, NORM_IGNORECASE, szTmp, -1, _CHT_IMEFILENAME3, -1 ) != 2 )
			&& ( CompareStringA( LCID_INVARIANT, NORM_IGNORECASE, szTmp, -1, _CHS_IMEFILENAME, -1 ) != 2 )
			&& ( CompareStringA( LCID_INVARIANT, NORM_IGNORECASE, szTmp, -1, _CHS_IMEFILENAME2, -1 ) != 2 )
			)
		{
			goto error;
		}
	}

	dwVerSize = GetFileVersionInfoSizeA( szTmp, &dwVerHandle );
	if ( dwVerSize )
	{
		lpVerBuffer = (LPVOID) ImeUiCallback_Malloc(dwVerSize);
		if ( lpVerBuffer )
		{
			if ( GetFileVersionInfoA( szTmp, dwVerHandle, dwVerSize, lpVerBuffer ) )
			{
				if ( VerQueryValueA( lpVerBuffer, "\\", &lpVerData, &cbVerData ) )
				{
					#define pVerFixedInfo ((VS_FIXEDFILEINFO FAR*)lpVerData)
					DWORD dwVer = pVerFixedInfo->dwFileVersionMS;
					dwVer = ( dwVer & 0x00ff0000 ) << 8 | ( dwVer & 0x000000ff ) << 16;
					if ( _GetReadingString ||
						dwLang == LANG_CHT && (
							dwVer == MAKEIMEVERSION(4, 2) || 
							dwVer == MAKEIMEVERSION(4, 3) || 
							dwVer == MAKEIMEVERSION(4, 4) || 
							dwVer == MAKEIMEVERSION(5, 0) ||
							dwVer == MAKEIMEVERSION(5, 1) ||
							dwVer == MAKEIMEVERSION(5, 2) ||
							dwVer == MAKEIMEVERSION(6, 0) )
							||
						dwLang == LANG_CHS && (
							dwVer == MAKEIMEVERSION(4, 1) ||
							dwVer == MAKEIMEVERSION(4, 2) ||
							dwVer == MAKEIMEVERSION(5, 3) ) )
					{
						dwRet[0] = dwVer | dwLang;
						dwRet[1] = pVerFixedInfo->dwFileVersionLS;
						ImeUiCallback_Free(lpVerBuffer);
						return dwRet[0];
					}
					#undef pVerFixedInfo
				}
			}
		}
		ImeUiCallback_Free(lpVerBuffer);
	}

// The flow comes here in the following conditions
// - Non Chinese IME input locale
// - Older Chinese IME
// - Other error cases
error:
	dwRet[0] = dwRet[1] = 0;
	return dwRet[uIndex];
}

static void GetReadingString(HWND hWnd)
{
	if ( g_bUILessMode )
	{
		return;
	}
	DWORD dwId = GetImeId();
	if ( ! dwId )
	{
		return;
	}
	
	HIMC himc;
	himc = _ImmGetContext(hWnd);
	if ( !himc )
		return;

	DWORD dwlen = 0;
	DWORD dwerr = 0;
	WCHAR wzBuf[16];	// We believe 16 wchars are big enough to hold reading string after having discussion with CHT IME team.
	WCHAR *wstr = wzBuf;
	bool unicode = FALSE;
	LPINPUTCONTEXT2 lpIMC = NULL;

	if ( _GetReadingString )
	{
		BOOL bVertical;
		UINT uMaxUiLen;
		dwlen =	_GetReadingString( himc, 0, NULL, (PINT)&dwerr, &bVertical, &uMaxUiLen);
		if ( dwlen )
		{
			if ( dwlen > COUNTOF(wzBuf) )
			{
				dwlen = COUNTOF(wzBuf);
			}
			dwlen = _GetReadingString( himc, dwlen, wstr, (PINT)&dwerr, &bVertical, &uMaxUiLen);
		}

		g_bHorizontalReading = bVertical == 0;
		unicode = true;
	}
	else //	IMEs that doesn't implement Reading String API
	{
		lpIMC = _ImmLockIMC(himc);
		
		// *** hacking code from Michael Yang ***
		
		LPBYTE p = 0;
		
		switch ( dwId ) {
		
		case IMEID_CHT_VER42: // New(Phonetic/ChanJie)IME98  : 4.2.x.x // Win98
		case IMEID_CHT_VER43: // New(Phonetic/ChanJie)IME98a : 4.3.x.x // WinMe, Win2k
		case IMEID_CHT_VER44: // New ChanJie IME98b          : 4.4.x.x // WinXP
			
			p = *(LPBYTE *)((LPBYTE)_ImmLockIMCC(lpIMC->hPrivate) + 24);
			if (!p) break;
			dwlen = *(DWORD *)(p + 7*4 + 32*4);	//m_dwInputReadStrLen
			dwerr = *(DWORD *)(p + 8*4 + 32*4);	//m_dwErrorReadStrStart
			wstr = (WCHAR *)(p + 56);
			unicode = TRUE;
			break;
		
		case IMEID_CHT_VER50: // 5.0.x.x // WinME
			
			p = *(LPBYTE *)((LPBYTE)_ImmLockIMCC(lpIMC->hPrivate) + 3*4); // PCKeyCtrlManager
			if (!p) break;
			p = *(LPBYTE *)((LPBYTE)p + 1*4 + 5*4 + 4*2 ); // = PCReading = &STypingInfo
			if (!p) break;
			dwlen = *(DWORD *)(p + 1*4 + (16*2+2*4) + 5*4 + 16);		//m_dwDisplayStringLength;
			dwerr = *(DWORD *)(p + 1*4 + (16*2+2*4) + 5*4 + 16 + 1*4);	//m_dwDisplayErrorStart;
			wstr = (WCHAR *)(p + 1*4 + (16*2+2*4) + 5*4);
			unicode = FALSE;
			break;

		case IMEID_CHT_VER51: // 5.1.x.x // IME2002(w/OfficeXP)
		case IMEID_CHT_VER52: // 5.2.x.x // (w/whistler)
        case IMEID_CHS_VER53: // 5.3.x.x // SCIME2k or MSPY3 (w/OfficeXP and Whistler)

			p = *(LPBYTE *)((LPBYTE)_ImmLockIMCC(lpIMC->hPrivate) + 4);   // PCKeyCtrlManager
			if (!p) break;
			p = *(LPBYTE *)((LPBYTE)p + 1*4 + 5*4);                       // = PCReading = &STypingInfo
			if (!p) break;
			dwlen = *(DWORD *)(p + 1*4 + (16*2+2*4) + 5*4 + 16 * 2);		//m_dwDisplayStringLength;
			dwerr = *(DWORD *)(p + 1*4 + (16*2+2*4) + 5*4 + 16 * 2 + 1*4);	//m_dwDisplayErrorStart;
			wstr  = (WCHAR *) (p + 1*4 + (16*2+2*4) + 5*4);
			unicode = TRUE;
			break;

		// the code tested only with Win 98 SE (MSPY 1.5/ ver 4.1.0.21)
		case IMEID_CHS_VER41:
		{
			int offset;
			offset = ( GetImeId( 1 ) >= 0x00000002 ) ? 8 : 7;

			p = *(LPBYTE *)((LPBYTE)_ImmLockIMCC(lpIMC->hPrivate) + offset * 4);
			if (!p) break;
			dwlen = *(DWORD *)(p + 7*4 + 16*2*4);
			dwerr = *(DWORD *)(p + 8*4 + 16*2*4);
			dwerr = min(dwerr, dwlen);
			wstr = (WCHAR *)(p + 6*4 + 16*2*1);
			unicode = TRUE;
			break;
		}

        case IMEID_CHS_VER42: // 4.2.x.x // SCIME98 or MSPY2 (w/Office2k, Win2k, WinME, etc)
		{
			int nTcharSize = IsNT() ? sizeof(WCHAR) : sizeof(char);
	        p = *(LPBYTE *)((LPBYTE)_ImmLockIMCC(lpIMC->hPrivate) + 1*4 + 1*4 + 6*4); // = PCReading = &STypintInfo
			if (!p) break;
	 		dwlen = *(DWORD *)(p + 1*4 + (16*2+2*4) + 5*4 + 16 * nTcharSize);		//m_dwDisplayStringLength;
			dwerr = *(DWORD *)(p + 1*4 + (16*2+2*4) + 5*4 + 16 * nTcharSize + 1*4);	//m_dwDisplayErrorStart;
			wstr  = (WCHAR *) (p + 1*4 + (16*2+2*4) + 5*4);                 //m_tszDisplayString
			unicode = IsNT() ? TRUE : FALSE;
		}
		}	// switch
		
		g_szCandidate[0][0] = 0;
		g_szCandidate[1][0] = 0;
		g_szCandidate[2][0] = 0;
		g_szCandidate[3][0] = 0;
	}	
	g_dwCount = dwlen;
	g_dwSelection = (DWORD)-1; // do not select any char
	if ( unicode )
	{
		int i;
		for ( i=0; (DWORD)i < dwlen ; i++ ) // dwlen > 0, if known IME : yutakah
		{
			if ( dwerr<=(DWORD)i && g_dwSelection==(DWORD)-1 ){ // select error char
				g_dwSelection = i;
			}
#ifdef UNICODE
			g_szCandidate[i][0] = wstr[i];
			g_szCandidate[i][1] = 0;
#else
			char mbc[3];
			mbc[1] = 0;
			mbc[2] = 0;
			WideCharToMultiByte(g_uCodePage, 0, wstr + i, 1, mbc, sizeof(mbc), NULL, NULL);

			g_szCandidate[i][0] = mbc[0];
			g_szCandidate[i][1] = mbc[1];
			g_szCandidate[i][2] = 0;
#endif
		}
		g_szCandidate[i][0] = 0;
	}
	else 
	{
		char *p = (char *)wstr;
		int i, j;
		for ( i=0, j=0; (DWORD)i < dwlen ; i++,j++ ) // dwlen > 0, if known IME : yutakah
		{
			if ( dwerr<=(DWORD)i && g_dwSelection==(DWORD)-1 ){
				g_dwSelection = (DWORD)j;
			}
#ifdef UNICODE
			MultiByteToWideChar( g_uCodePage, 0, p + i, 1 + ( _IsLeadByte( p[i] ) ? 1 : 0 ),
				g_szCandidate[j], 1 );
			if ( _IsLeadByte( p[i] ) )
			{
				i++;
			}
#else
			g_szCandidate[j][0] = p[i];
			g_szCandidate[j][1] = 0;
			g_szCandidate[j][2] = 0;
			if (_IsLeadByte(p[i]))
			{
				i++;
				g_szCandidate[j][1] = p[i];
			}
#endif
		}
		g_szCandidate[j][0] = 0;
		g_dwCount = j;
	}
	if ( !_GetReadingString )
	{
		_ImmUnlockIMCC( lpIMC->hPrivate );
		_ImmUnlockIMC( himc );
		GetReadingWindowOrientation( dwId );
	}
	_ImmReleaseContext(hWnd, himc);

	g_bReadingWindow = true;
	if (g_bHorizontalReading) {
		g_iReadingError = -1;
		g_szReadingString[0] = 0;
		for (UINT i = 0; i < g_dwCount; i++) {
			if (g_dwSelection == (DWORD)i)
				g_iReadingError = lstrlen(g_szReadingString);
			LPCTSTR pszTmp = g_szCandidate[i];
			wcscat_s(g_szReadingString, COUNTOF(g_szReadingString), pszTmp);
		}
	}
	g_uCandPageSize = MAX_CANDLIST;
}


static struct {
	bool m_bCtrl;
	bool m_bShift;
	bool m_bAlt;
	UINT m_uVk;
}
aHotKeys[] = {
	false,	false,	false,	VK_APPS,
	true,	false,	false,	'8',
	true,	false,	false,	'Y',
	true,	false,	false,	VK_DELETE,
	true,	false,	false,	VK_F7,
	true,	false,	false,	VK_F9,
	true,	false,	false,	VK_F10,
	true,	false,	false,	VK_F11,
	true,	false,	false,	VK_F12,
	false,	false,	false,	VK_F2,
	false,	false,	false,	VK_F3,
	false,	false,	false,	VK_F4,
	false,	false,	false,	VK_F5,
	false,	false,	false,	VK_F10,
	false,	true,	false,	VK_F6,
	false,	true,	false,	VK_F7,
	false,	true,	false,	VK_F8,
	true,	true,	false,	VK_F10,
	true,	true,	false,	VK_F11,
	true,	false,	false,	VK_CONVERT,
	true,	false,	false,	VK_SPACE,
	true,	false,	true,	0xbc,	// Alt + Ctrl + ',': SW keyboard for Trad. Chinese IME
	true,   false,  false,  VK_TAB, // ATOK2005's Ctrl+TAB
};

//
// Ignores specific keys when IME is on. Returns true if the message is a hot key to ignore.
// - Caller doesn't have to check whether IME is on.
// - This function must be called before TranslateMessage() is called.
//
bool ImeUi_IgnoreHotKey( const MSG* pmsg )
{
	if ( !g_bInitialized || !pmsg )
		return false;

	if (pmsg->wParam == VK_PROCESSKEY && (pmsg->message == WM_KEYDOWN || pmsg->message == WM_SYSKEYDOWN))
	{
		bool bCtrl, bShift, bAlt;
		UINT uVkReal = _ImmGetVirtualKey(pmsg->hwnd);
		// special case #1 - VK_JUNJA toggles half/full width input mode in Korean IME.
		// This VK (sent by Alt+'=' combo) is ignored regardless of the modifier state.
		if ( uVkReal == VK_JUNJA )
		{
			return true;
		}
		// special case #2 - disable right arrow key that switches the candidate list to expanded mode in CHT IME.
		if ( uVkReal == VK_RIGHT && g_bCandList && GETLANG() == LANG_CHT)
		{
			return true;
		}
#ifndef ENABLE_HANJA_KEY
		// special case #3 - we disable VK_HANJA key because 1. some Korean fonts don't Hanja and 2. to reduce testing cost.
		if ( uVkReal == VK_HANJA && GETPRIMLANG() == LANG_KOREAN )
		{
			return true;
		}
#endif
		bCtrl = (GetKeyState(VK_CONTROL) & 0x8000) ? true : false;
		bShift = (GetKeyState(VK_SHIFT) & 0x8000) ? true : false;
		bAlt = (GetKeyState(VK_MENU) & 0x8000) ? true : false;
		for (int i = 0; i < COUNTOF(aHotKeys); i++) {
			if (aHotKeys[i].m_bCtrl == bCtrl &&
				aHotKeys[i].m_bShift == bShift &&
				aHotKeys[i].m_bAlt == bAlt &&
				aHotKeys[i].m_uVk == uVkReal)
				return true;
		}
	}
	return false;
}

void ImeUi_FinalizeString(bool bSend)
{
	HIMC himc;
	static bool bProcessing = false; // to avoid infinite recursion
	if ( !g_bInitialized || bProcessing || NULL == ( himc = _ImmGetContext( g_hwndCurr ) ) )
		return;
	bProcessing = true;

	if (g_dwIMELevel == 2 && bSend)
	{
		// Send composition string to app.
		LONG lRet = lstrlen( g_szCompositionString );
		assert( lRet >= 2);
		// In case of CHT IME, don't send the trailing double byte space, if it exists.
#ifdef UNICODE
		if ( GETLANG() == LANG_CHT && (lRet >= 1)
			&& g_szCompositionString[lRet - 1] == 0x3000 )
		{
			lRet--;
		}
#else
		if ( GETLANG() == LANG_CHT && (lRet >= 2)
			&& (BYTE)( g_szCompositionString[lRet - 2] ) == 0xa1
			&& (BYTE)( g_szCompositionString[lRet - 1] ) == 0x40 )
		{
			lRet -= 2;
		}
#endif
		_SendCompString();
	}

	InitCompStringData();
	// clear composition string in IME
	_ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
	if (g_bUILessMode)
	{
		// For some reason ImmNotifyIME doesn't work on DaYi and Array CHT IMEs. Cancel composition string by setting zero-length string.
		ImmSetCompositionString(himc, SCS_SETSTR, TEXT(""), sizeof(TCHAR), TEXT(""), sizeof(TCHAR));
	}
	// the following line is necessary as Korean IME doesn't close cand list when comp string is cancelled.
	_ImmNotifyIME( himc, NI_CLOSECANDIDATE, 0, 0 );	
	_ImmReleaseContext(g_hwndCurr, himc);
	// Zooty2 RAID #4759: Sometimes application doesn't receive IMN_CLOSECANDIDATE on Alt+Tab
	// So the same code for IMN_CLOSECANDIDATE is replicated here.
	CloseCandidateList();
	bProcessing = false;
	return;
}

static void SetCompStringColor()
{
	// change color setting according to current IME level.
	DWORD dwTranslucency = (g_dwIMELevel == 2) ? 0xff000000 : ((DWORD)gSkinIME.compTranslucence << 24);
	gSkinCompStr.colorInput			= dwTranslucency | gSkinIME.compColorInput;
	gSkinCompStr.colorTargetConv	= dwTranslucency | gSkinIME.compColorTargetConv;
	gSkinCompStr.colorConverted		= dwTranslucency | gSkinIME.compColorConverted;
	gSkinCompStr.colorTargetNotConv = dwTranslucency | gSkinIME.compColorTargetNotConv;
	gSkinCompStr.colorInputErr		= dwTranslucency | gSkinIME.compColorInputErr;
}

static void SetSupportLevel( DWORD dwImeLevel )
{
	if ( dwImeLevel < 2 || 3 < dwImeLevel )
		return;
	if ( GETPRIMLANG() == LANG_KOREAN )
	{
		dwImeLevel = 3;
	}
	g_dwIMELevel = dwImeLevel;
	// cancel current composition string.
	ImeUi_FinalizeString();
	SetCompStringColor();
}

void ImeUi_SetSupportLevel(DWORD dwImeLevel)
{
	if ( !g_bInitialized )
		return;
	g_dwIMELevelSaved = dwImeLevel;
	SetSupportLevel( dwImeLevel );
}

void ImeUi_SetAppearance( const IMEUI_APPEARANCE* pia )
{
	if ( !g_bInitialized || NULL == pia )
		return;
	gSkinIME = *pia;
	gSkinIME.symbolColor			&= 0xffffff; // mask translucency
	gSkinIME.symbolColorOff			&= 0xffffff; // mask translucency
	gSkinIME.symbolColorText		&= 0xffffff; // mask translucency
	gSkinIME.compColorInput			&= 0xffffff; // mask translucency
	gSkinIME.compColorTargetConv	&= 0xffffff; // mask translucency
	gSkinIME.compColorConverted		&= 0xffffff; // mask translucency
	gSkinIME.compColorTargetNotConv	&= 0xffffff; // mask translucency
	gSkinIME.compColorInputErr		&= 0xffffff; // mask translucency
	SetCompStringColor();
}

void ImeUi_GetAppearance( IMEUI_APPEARANCE* pia )
{
	if ( g_bInitialized && pia )
	{
		*pia = gSkinIME;
	}
}

static void CheckToggleState()
{
	CheckInputLocale();

	// In Vista, we have to use TSF since few IMM functions don't work as expected.
	// WARNING: Because of timing, g_dwState and g_bChineseIME may not be updated 
	// immediately after the change on IME states by user.
	if ( g_bUILessMode )
	{
		return;
	}

	bool bIme = _ImmIsIME( g_hklCurrent ) != 0
		&& ( ( 0xF0000000 & (DWORD)g_hklCurrent ) == 0xE0000000 ); // Hack to detect IME correctly. When IME is running as TIP, ImmIsIME() returns true for CHT US keyboard.
	g_bChineseIME = ( GETPRIMLANG() == LANG_CHINESE ) && bIme;

	HIMC himc;
	if (NULL != (himc = _ImmGetContext(g_hwndCurr))) {
		if (g_bChineseIME) {
			DWORD dwConvMode, dwSentMode;
			_ImmGetConversionStatus(himc, &dwConvMode, &dwSentMode);
			g_dwState = ( dwConvMode & IME_CMODE_NATIVE ) ? IMEUI_STATE_ON : IMEUI_STATE_ENGLISH;
		}
		else
		{
			g_dwState = ( bIme && _ImmGetOpenStatus( himc ) != 0 ) ? IMEUI_STATE_ON : IMEUI_STATE_OFF;
		}
		_ImmReleaseContext(g_hwndCurr, himc);
	}
	else
		g_dwState = IMEUI_STATE_OFF;
}

void ImeUi_SetInsertMode(bool bInsert)
{
	if ( !g_bInitialized )
		return;
	g_bInsertMode = bInsert;
}

bool ImeUi_GetCaretStatus()
{
	return !g_bInitialized || !g_szCompositionString[0];
}

void ImeUi_SetScreenDimension(UINT width, UINT height)
{
	if ( !g_bInitialized )
		return;
	g_screenWidth = width;
	g_screenHeight = height;
}

// this function is used only in brief time in CHT IME handling, so accelerator isn't processed.
static void _PumpMessage()
{
	MSG msg;
	while (PeekMessageA(&msg, NULL, 0, 0, PM_NOREMOVE)) {
		if (!GetMessageA(&msg, NULL, 0, 0)) {
			PostQuitMessage(msg.wParam);
			return;
		}
//		if (0 == TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
			TranslateMessage(&msg);
			DispatchMessageA(&msg);
//		}
	}
}

static void GetReadingWindowOrientation( DWORD dwId )
{
	g_bHorizontalReading = ( g_hklCurrent == _CHS_HKL ) || ( g_hklCurrent == _CHT_HKL_NEW_CHANG_JIE ) || ( dwId == 0 );
	if (!g_bHorizontalReading && IMEID_LANG( dwId ) == LANG_CHT )
	{
		char szRegPath[MAX_PATH];
		HKEY hkey;
		DWORD dwVer = IMEID_VER( dwId );
		strcpy_s(szRegPath, COUNTOF(szRegPath), "software\\microsoft\\windows\\currentversion\\");
		strcat_s(szRegPath, COUNTOF(szRegPath), (dwVer >= MAKEIMEVERSION(5, 1)) ? "MSTCIPH" : "TINTLGNT");
		LONG lRc = RegOpenKeyExA(HKEY_CURRENT_USER, szRegPath, 0, KEY_READ, &hkey);
		if (lRc == ERROR_SUCCESS) {
			DWORD dwSize = sizeof(DWORD), dwMapping, dwType;
			lRc = RegQueryValueExA(hkey, "keyboard mapping", NULL, &dwType, (PBYTE)&dwMapping, &dwSize);
			if (lRc == ERROR_SUCCESS) {
				if (
					( dwVer <= MAKEIMEVERSION( 5, 0 ) && 
						((BYTE)dwMapping == 0x22 || (BYTE)dwMapping == 0x23)
					) ||
					( ( dwVer == MAKEIMEVERSION( 5, 1 ) || dwVer == MAKEIMEVERSION( 5, 2 ) ) &&
						((BYTE)dwMapping >= 0x22 && (BYTE)dwMapping <= 0x24)
					)
				   )
				{
					g_bHorizontalReading = true;
				}
			}
			RegCloseKey( hkey );
		}
	}
}

void ImeUi_ToggleLanguageBar(BOOL bRestore)
{
	static BOOL prevRestore = TRUE;
	bool bCheck = ( prevRestore == TRUE || bRestore == TRUE );
	prevRestore = bRestore;
	if ( !bCheck )
		return;

	static int iShowStatusWindow = -1;
	if ( iShowStatusWindow == -1 )
	{
		iShowStatusWindow = IsNT() && g_osi.dwMajorVersion >= 5 && 
			( g_osi.dwMinorVersion > 1 || ( g_osi.dwMinorVersion == 1 && lstrlenA( g_osi.szCSDVersion ) ) ) ? 1 : 0;
	}
	HWND hwndImeDef = _ImmGetDefaultIMEWnd(g_hwndCurr);
	if ( hwndImeDef && bRestore && iShowStatusWindow )
		SendMessageA(hwndImeDef, WM_IME_CONTROL, IMC_OPENSTATUSWINDOW, 0);
	HRESULT hr;
	hr = CoInitialize(NULL);
	if (SUCCEEDED(hr))
	{
		ITfLangBarMgr* plbm = NULL;
		hr = CoCreateInstance(CLSID_TF_LangBarMgr, NULL, CLSCTX_INPROC_SERVER, __uuidof(ITfLangBarMgr), (void**)&plbm);
		if (SUCCEEDED(hr) && plbm)
		{
			DWORD dwCur;
			ULONG uRc;
			if (SUCCEEDED(hr))
			{
				if (bRestore)
				{
					if (g_dwPrevFloat)
						hr = plbm->ShowFloating(g_dwPrevFloat);
				}
				else
				{
					hr = plbm->GetShowFloatingStatus(&dwCur);
					if (SUCCEEDED(hr))
						g_dwPrevFloat = dwCur;
					if ( !( g_dwPrevFloat & TF_SFT_DESKBAND ) )
					{
						hr = plbm->ShowFloating(TF_SFT_HIDDEN);
					}
				}
			}
			uRc = plbm->Release();
		}
		CoUninitialize();
	}
	if ( hwndImeDef && !bRestore )
	{
		// The following OPENSTATUSWINDOW is required to hide ATOK16 toolbar (FS9:#7546)
		SendMessageA(hwndImeDef, WM_IME_CONTROL, IMC_OPENSTATUSWINDOW, 0);
		SendMessageA(hwndImeDef, WM_IME_CONTROL, IMC_CLOSESTATUSWINDOW, 0);
	}
}

bool ImeUi_IsSendingKeyMessage()
{
	return bIsSendingKeyMessage;
}

static void OnInputLangChangeWorker()
{
	if ( !g_bUILessMode )
	{
		g_iCandListIndexBase = ( g_hklCurrent == _CHT_HKL_DAYI ) ? 0 : 1;
	}
	SetImeApi();
}

static void OnInputLangChange()
{
	UINT uLang = GETPRIMLANG();
	CheckToggleState();
	OnInputLangChangeWorker();
	if (uLang != GETPRIMLANG())
	{
		// Korean IME always uses level 3 support.
		// Other languages use the level that is specified by ImeUi_SetSupportLevel()
		SetSupportLevel( ( GETPRIMLANG() == LANG_KOREAN ) ? 3 : g_dwIMELevelSaved );
	}
	HWND hwndImeDef = _ImmGetDefaultIMEWnd(g_hwndCurr);
	if ( hwndImeDef )
	{
		// Fix for Zooty #3995: prevent CHT IME toobar from showing up
		SendMessageA(hwndImeDef, WM_IME_CONTROL, IMC_OPENSTATUSWINDOW, 0);
		SendMessageA(hwndImeDef, WM_IME_CONTROL, IMC_CLOSESTATUSWINDOW, 0);
	}
}

static void SetImeApi()
{
	_GetReadingString = NULL;
	_ShowReadingWindow = NULL;
	if(g_bUILessMode)
		return;

	char szImeFile[MAX_PATH + 1];
	HKL kl = g_hklCurrent;
	if ( _ImmGetIMEFileNameA( kl, szImeFile, sizeof( szImeFile ) - 1 ) <= 0 )
		return;
	HMODULE hIme = LoadLibraryA( szImeFile );
	if ( !hIme )
		return;
	_GetReadingString = (UINT (WINAPI*)(HIMC, UINT, LPWSTR, PINT, BOOL*, PUINT))
		(GetProcAddress(hIme, "GetReadingString"));
	_ShowReadingWindow =(BOOL (WINAPI*)(HIMC himc, BOOL))
		(GetProcAddress(hIme, "ShowReadingWindow"));
	if ( _ShowReadingWindow )
	{
		HIMC himc;
		if ( NULL != ( himc = _ImmGetContext( g_hwndCurr ) ) )
		{
			_ShowReadingWindow( himc, false );
			_ImmReleaseContext( g_hwndCurr, himc );
		}
	}
}

static void CheckInputLocale()
{
	static HKL hklPrev = 0;
	g_hklCurrent = GetKeyboardLayout( 0 );
	if ( hklPrev == g_hklCurrent )
	{
		return;
	}
	hklPrev = g_hklCurrent;
	switch ( GETPRIMLANG() )
	{
	// Simplified Chinese
	case LANG_CHINESE:
		g_bVerticalCand = true;
		switch ( GETSUBLANG() )
		{
		case SUBLANG_CHINESE_SIMPLIFIED:
			g_pszIndicatior = g_aszIndicator[INDICATOR_CHS];
			//g_bVerticalCand = GetImeId() == 0;
			g_bVerticalCand = false;
			break;
		case SUBLANG_CHINESE_TRADITIONAL:
			g_pszIndicatior = g_aszIndicator[INDICATOR_CHT];
			break;
		default:	// unsupported sub-language
			g_pszIndicatior = g_aszIndicator[INDICATOR_NON_IME];
			break;
		}
		break;
	// Korean
	case LANG_KOREAN:
		g_pszIndicatior = g_aszIndicator[INDICATOR_KOREAN];
		g_bVerticalCand = false;
		break;
 	// Japanese
	case LANG_JAPANESE:
		g_pszIndicatior = g_aszIndicator[INDICATOR_JAPANESE];
		g_bVerticalCand = true;
		break;		   
	default:
		g_pszIndicatior = g_aszIndicator[INDICATOR_NON_IME];
	}
	char szCodePage[8];
	int iRc = GetLocaleInfoA( MAKELCID( GETLANG(), SORT_DEFAULT ), LOCALE_IDEFAULTANSICODEPAGE, szCodePage, COUNTOF( szCodePage ) ); iRc;
	g_uCodePage = _strtoul( szCodePage, NULL, 0 );
	for ( int i = 0; i < 256; i++ )
	{
		LeadByteTable[i] = (BYTE)IsDBCSLeadByteEx( g_uCodePage, (BYTE)i );
	}
}

void ImeUi_SetWindow(HWND hwnd)
{
	g_hwndCurr = hwnd;
	g_disableCicero.DisableCiceroOnThisWnd( hwnd );
}

UINT ImeUi_GetInputCodePage()
{
	return g_uCodePage;
}

DWORD ImeUi_GetFlags()
{
	return g_dwImeUiFlags;
}

void ImeUi_SetFlags( DWORD dwFlags, bool bSet )
{
	if ( bSet )
	{
		g_dwImeUiFlags |= dwFlags;
	}
	else
	{
		g_dwImeUiFlags &= ~dwFlags;
	}
}

///////////////////////////////////////////////////////////////////////////////
//
//	CTsfUiLessMode methods
//
///////////////////////////////////////////////////////////////////////////////

//
//	SetupSinks()
//	Set up sinks. A sink is used to receive a Text Service Framework event.
//  CUIElementSink implements multiple sink interfaces to receive few different TSF events.
//
BOOL CTsfUiLessMode::SetupSinks()
{
	// ITfThreadMgrEx is available on Vista or later.
	HRESULT hr;
    hr = CoCreateInstance(CLSID_TF_ThreadMgr, 
                          NULL, 
                          CLSCTX_INPROC_SERVER, 
                          __uuidof(ITfThreadMgrEx), 
                          (void**)&m_tm);

    if (hr != S_OK)
    {
        return FALSE;
    }

    // ready to start interacting
	TfClientId cid;	// not used
    if (FAILED(m_tm->ActivateEx(&cid, TF_TMAE_UIELEMENTENABLEDONLY)))
    {
        return FALSE;
    }

	// Setup sinks
	BOOL bRc = FALSE;
    m_TsfSink = new CUIElementSink();
	if (m_TsfSink)
	{
		ITfSource *srcTm;
		if (SUCCEEDED(hr = m_tm->QueryInterface(__uuidof(ITfSource), (void **)&srcTm)))
		{
			// Sink for reading window change
			if (SUCCEEDED(hr = srcTm->AdviseSink(__uuidof(ITfUIElementSink), (ITfUIElementSink*)m_TsfSink, &m_dwUIElementSinkCookie)))
			{
				// Sink for input locale change
				if (SUCCEEDED(hr = srcTm->AdviseSink(__uuidof(ITfInputProcessorProfileActivationSink), (ITfInputProcessorProfileActivationSink*)m_TsfSink, &m_dwAlpnSinkCookie)))
				{
					if (SetupCompartmentSinks())	// Setup compartment sinks for the first time
					{
						bRc = TRUE;
					}
				}
			}
			srcTm->Release();
		}
	}
	return bRc;
}

void CTsfUiLessMode::ReleaseSinks()
{
	HRESULT hr;
	ITfSource *source;

	// Remove all sinks
	if ( m_tm && SUCCEEDED(m_tm->QueryInterface(__uuidof(ITfSource), (void **)&source)))
	{
        hr = source->UnadviseSink(m_dwUIElementSinkCookie);
		hr = source->UnadviseSink(m_dwAlpnSinkCookie);
        source->Release();
		SetupCompartmentSinks(TRUE);	// Remove all compartment sinks
		m_tm->Deactivate();
		SAFE_RELEASE(m_tm);
		SAFE_RELEASE(m_TsfSink);
	}	
}

CTsfUiLessMode::CUIElementSink::CUIElementSink()
{
    _cRef = 1;
}


CTsfUiLessMode::CUIElementSink::~CUIElementSink()
{
}

STDAPI CTsfUiLessMode::CUIElementSink::QueryInterface(REFIID riid, void **ppvObj)
{
    if (ppvObj == NULL)
        return E_INVALIDARG;

    *ppvObj = NULL;

    if (IsEqualIID(riid, IID_IUnknown))
	{
        *ppvObj = reinterpret_cast<IUnknown *>(this);
	}
	else if (IsEqualIID(riid, __uuidof(ITfUIElementSink)))
    {
        *ppvObj = (ITfUIElementSink *)this;
    }
	else if (IsEqualIID(riid, __uuidof(ITfInputProcessorProfileActivationSink)))
	{
		*ppvObj = (ITfInputProcessorProfileActivationSink*)this;
	}
	else if (IsEqualIID(riid, __uuidof(ITfCompartmentEventSink)))
	{
		*ppvObj = (ITfCompartmentEventSink*)this;
	}

    if (*ppvObj)
    {
        AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

STDAPI_(ULONG) CTsfUiLessMode::CUIElementSink::AddRef()
{
    return ++_cRef;
}

STDAPI_(ULONG) CTsfUiLessMode::CUIElementSink::Release()
{
    LONG cr = --_cRef;

    if (_cRef == 0)
    {
        delete this;
    }

    return cr;
}

STDAPI CTsfUiLessMode::CUIElementSink::BeginUIElement(DWORD dwUIElementId, BOOL *pbShow)
{
    ITfUIElement *pElement = GetUIElement(dwUIElementId);
    if (!pElement)
        return E_INVALIDARG;

    ITfReadingInformationUIElement   *preading = NULL;
	ITfCandidateListUIElement		*pcandidate = NULL;
	*pbShow = FALSE;
	if (!g_bCandList && SUCCEEDED(pElement->QueryInterface(__uuidof(ITfReadingInformationUIElement),
                                      (void **)&preading)))
    {
		MakeReadingInformationString(preading);
        preading->Release();
    }
	else if (SUCCEEDED(pElement->QueryInterface(__uuidof(ITfCandidateListUIElement),
									(void **)&pcandidate)))
	{
		m_nCandidateRefCount++;
		MakeCandidateStrings(pcandidate);
		pcandidate->Release();
    }

    pElement->Release();
    return S_OK;
}

STDAPI CTsfUiLessMode::CUIElementSink::UpdateUIElement(DWORD dwUIElementId)
{
    ITfUIElement *pElement = GetUIElement(dwUIElementId);
    if (!pElement)
        return E_INVALIDARG;

    ITfReadingInformationUIElement   *preading = NULL;
    ITfCandidateListUIElement		*pcandidate = NULL;
	if (!g_bCandList && SUCCEEDED(pElement->QueryInterface(__uuidof(ITfReadingInformationUIElement),
                                      (void **)&preading)))
    {
		MakeReadingInformationString(preading);
        preading->Release();
    }
	else if (SUCCEEDED(pElement->QueryInterface(__uuidof(ITfCandidateListUIElement),
											(void **)&pcandidate)))
	{
		MakeCandidateStrings(pcandidate);
		pcandidate->Release();
    }

    pElement->Release();
    return S_OK;
}

STDAPI CTsfUiLessMode::CUIElementSink::EndUIElement(DWORD dwUIElementId)
{
    ITfUIElement *pElement = GetUIElement(dwUIElementId);
    if (!pElement)
        return E_INVALIDARG;

    ITfReadingInformationUIElement   *preading = NULL;
	if (!g_bCandList && SUCCEEDED(pElement->QueryInterface(__uuidof(ITfReadingInformationUIElement),
                                      (void **)&preading)))
    {
		g_dwCount = 0;
        preading->Release();
    }

	ITfCandidateListUIElement   *pcandidate = NULL;
	if (SUCCEEDED(pElement->QueryInterface(__uuidof(ITfCandidateListUIElement),
									(void **)&pcandidate)))
	{
		m_nCandidateRefCount--;
		if (m_nCandidateRefCount == 0)
			CloseCandidateList();
		pcandidate->Release();
	}

    pElement->Release();
    return S_OK;
}

void CTsfUiLessMode::UpdateImeState(BOOL bResetCompartmentEventSink)
{
	ITfCompartmentMgr* pcm;
	ITfCompartment* pTfOpenMode = NULL;
	ITfCompartment* pTfConvMode = NULL;
	if ( GetCompartments( &pcm, &pTfOpenMode, &pTfConvMode ) )
	{
		VARIANT valOpenMode;
		VARIANT valConvMode;
		pTfOpenMode->GetValue( &valOpenMode );
		pTfConvMode->GetValue( &valConvMode );
		if ( valOpenMode.vt == VT_I4 )
		{
			if ( g_bChineseIME )
			{
				g_dwState = valOpenMode.lVal != 0 && valConvMode.lVal != 0 ? IMEUI_STATE_ON : IMEUI_STATE_ENGLISH;
			}
			else
			{
				g_dwState = valOpenMode.lVal != 0 ? IMEUI_STATE_ON : IMEUI_STATE_OFF;
			}
		}
		VariantClear( &valOpenMode );
		VariantClear( &valConvMode );

		if ( bResetCompartmentEventSink )
		{
			SetupCompartmentSinks( FALSE, pTfOpenMode, pTfConvMode );	// Reset compartment sinks
		}
		pTfOpenMode->Release();
		pTfConvMode->Release();
		pcm->Release();
	}
}

STDAPI CTsfUiLessMode::CUIElementSink::OnActivated(DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid,
		REFGUID guidProfile, HKL hkl, DWORD dwFlags)
{
	static GUID TF_PROFILE_DAYI = { 0x037B2C25, 0x480C, 0x4D7F, 0xB0, 0x27, 0xD6, 0xCA, 0x6B, 0x69, 0x78, 0x8A };
	g_iCandListIndexBase = IsEqualGUID( TF_PROFILE_DAYI, guidProfile ) ? 0 : 1;   
	if ( IsEqualIID( catid, GUID_TFCAT_TIP_KEYBOARD ) && ( dwFlags & TF_IPSINK_FLAG_ACTIVE ) )
	{
		g_bChineseIME = ( dwProfileType & TF_PROFILETYPE_INPUTPROCESSOR ) && langid == LANG_CHT;
		if ( dwProfileType & TF_PROFILETYPE_INPUTPROCESSOR )
		{
			UpdateImeState(TRUE);
		}
		else
			g_dwState = IMEUI_STATE_OFF;
		OnInputLangChange();
	}
    return S_OK;
}

STDAPI CTsfUiLessMode::CUIElementSink::OnChange(REFGUID rguid)
{
	UpdateImeState();
    return S_OK;
}

void CTsfUiLessMode::MakeReadingInformationString(ITfReadingInformationUIElement* preading)
{
	UINT cchMax;
	UINT uErrorIndex = 0;
	BOOL fVertical;
	DWORD dwFlags;

	preading->GetUpdatedFlags(&dwFlags);
	preading->GetMaxReadingStringLength(&cchMax);
	preading->GetErrorIndex(&uErrorIndex);	// errorIndex is zero-based
	preading->IsVerticalOrderPreferred(&fVertical);
	g_iReadingError = (int)uErrorIndex;
	g_bHorizontalReading = !fVertical;
	g_bReadingWindow = true;
	g_uCandPageSize = MAX_CANDLIST;
	g_dwSelection = g_iReadingError ? g_iReadingError - 1 : (DWORD)-1;
	g_iReadingError--;	// g_iReadingError is used only in horizontal window, and has to be -1 if there's no error.
#ifndef UNICODE
	if ( g_iReadingError > 0 )
	{
		// convert g_iReadingError to byte based
		LPCSTR pszNext = g_szReadingString;
		for (int i = 0; i < g_iReadingError && pszNext && *pszNext; ++i)
		{
			pszNext = CharNext(pszNext);
		}
		if (pszNext)	// should be non-NULL, but just in case
		{
			g_iReadingError = pszNext - g_szReadingString;
		}
	}
#endif

	BSTR bstr;
	if (SUCCEEDED(preading->GetString(&bstr)))
	{
		if (bstr)
		{
#ifndef UNICODE
			char szStr[COUNTOF(g_szReadingString)*2];
			szStr[0] = 0;
			int iRc = WideCharToMultiByte(CP_ACP, 0, bstr, -1, szStr, sizeof(szStr), NULL, NULL);
			if (iRc >= sizeof(szStr))
			{
				szStr[sizeof(szStr)-1] = 0;
			}
			wcscpy_s( g_szReadingString, COUNTOF(g_szReadingString), szStr );
#else
			wcscpy_s( g_szReadingString, COUNTOF(g_szReadingString), bstr );
#endif
			g_dwCount = cchMax;
			LPCTSTR pszSource = g_szReadingString;
			if ( fVertical )
			{
				// for vertical reading window, copy each character to g_szCandidate array.
				for ( UINT i = 0; i < cchMax; i++ )
				{
					LPTSTR pszDest = g_szCandidate[i];
					if ( *pszSource )
					{
						LPTSTR pszNextSrc = CharNext(pszSource);
						SIZE_T size = (LPSTR)pszNextSrc - (LPSTR)pszSource;
						CopyMemory( pszDest, pszSource, size );
						pszSource = pszNextSrc;
						pszDest += size;
					}
					*pszDest = 0;
				}
			}
			else
			{
				g_szCandidate[0][0] = TEXT(' ');	// hack to make rendering happen
			}
			SysFreeString(bstr);
		}
	}
}

void CTsfUiLessMode::MakeCandidateStrings(ITfCandidateListUIElement* pcandidate)
{
	UINT uIndex = 0;
	UINT uCount = 0;
	UINT uCurrentPage = 0;
	UINT *IndexList = NULL;
	UINT uPageCnt = 0;
	DWORD dwPageStart = 0;
	DWORD dwPageSize = 0;
	BSTR bstr;

	pcandidate->GetSelection(&uIndex);
	pcandidate->GetCount(&uCount);
	pcandidate->GetCurrentPage(&uCurrentPage);
	g_dwSelection = (DWORD)uIndex;
	g_dwCount = (DWORD)uCount;
	g_bCandList = true;
	g_bReadingWindow = false;

	pcandidate->GetPageIndex(NULL, 0, &uPageCnt);
	if(uPageCnt > 0)
	{
		IndexList = (UINT *)ImeUiCallback_Malloc(sizeof(UINT)*uPageCnt);
		if(IndexList)
		{
			pcandidate->GetPageIndex(IndexList, uPageCnt, &uPageCnt);
			dwPageStart = IndexList[uCurrentPage];
			dwPageSize = (uCurrentPage < uPageCnt-1) ? 
				min(uCount, IndexList[uCurrentPage+1]) - dwPageStart:
				uCount - dwPageStart;
		}
	}

	g_uCandPageSize = min(dwPageSize, MAX_CANDLIST);
	g_dwSelection = g_dwSelection - dwPageStart;

	memset(&g_szCandidate, 0, sizeof(g_szCandidate));
	for (UINT i = dwPageStart, j = 0; (DWORD)i < g_dwCount && j < g_uCandPageSize; i++, j++)
	{
		if (SUCCEEDED(pcandidate->GetString( i, &bstr )))
		{
			if(bstr)
			{
#ifndef UNICODE
				char szStr[COUNTOF(g_szCandidate[0])*2];
				szStr[0] = 0;
				int iRc = WideCharToMultiByte(CP_ACP, 0, bstr, -1, szStr, sizeof(szStr), NULL, NULL);
				if (iRc >= sizeof(szStr))
				{
					szStr[sizeof(szStr)-1] = 0;
				}
				ComposeCandidateLine( j, szStr );
#else
				ComposeCandidateLine( j, bstr );
#endif
				SysFreeString(bstr);
			}
		}
	}

	if (GETPRIMLANG() == LANG_KOREAN)
	{
		g_dwSelection = (DWORD)-1;
	}

	if(IndexList)
	{
		ImeUiCallback_Free(IndexList);
	}
}

ITfUIElement* CTsfUiLessMode::GetUIElement(DWORD dwUIElementId)
{
    ITfUIElementMgr *puiem;
    ITfUIElement *pElement = NULL;

    if (SUCCEEDED(m_tm->QueryInterface(__uuidof(ITfUIElementMgr), (void **)&puiem)))
    {
        puiem->GetUIElement(dwUIElementId, &pElement);
        puiem->Release();
    }

    return pElement;
}

BOOL CTsfUiLessMode::CurrentInputLocaleIsIme()
{
	BOOL ret = FALSE;
	HRESULT hr;

	ITfInputProcessorProfiles *pProfiles;
	hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER, __uuidof(ITfInputProcessorProfiles), (LPVOID*)&pProfiles);
	if (SUCCEEDED(hr))
	{
		ITfInputProcessorProfileMgr *pProfileMgr;
		hr = pProfiles->QueryInterface(__uuidof(ITfInputProcessorProfileMgr), (LPVOID*)&pProfileMgr);
		if (SUCCEEDED(hr))
		{
			TF_INPUTPROCESSORPROFILE tip;
			hr = pProfileMgr->GetActiveProfile( GUID_TFCAT_TIP_KEYBOARD, &tip );
			if (SUCCEEDED(hr))
			{
				ret = ( tip.dwProfileType & TF_PROFILETYPE_INPUTPROCESSOR ) != 0;
			}
			pProfileMgr->Release();
		}
		pProfiles->Release();
	}
	return ret;
}

// Sets up or removes sink for UI element. 
// UI element sink should be removed when IME is disabled,
// otherwise the sink can be triggered when a game has multiple instances of IME UI library.
void CTsfUiLessMode::EnableUiUpdates(bool bEnable)
{
	if ( m_tm == NULL ||
		 ( bEnable && m_dwUIElementSinkCookie != TF_INVALID_COOKIE )  ||
		 ( !bEnable && m_dwUIElementSinkCookie == TF_INVALID_COOKIE ) )
	{
		return;
	}
	ITfSource *srcTm = NULL;
	HRESULT hr = E_FAIL;
	if (SUCCEEDED(hr = m_tm->QueryInterface(__uuidof(ITfSource), (void **)&srcTm)))
	{
		if ( bEnable )
		{
			hr = srcTm->AdviseSink(__uuidof(ITfUIElementSink), (ITfUIElementSink*)m_TsfSink, &m_dwUIElementSinkCookie);
		}
		else
		{
			hr = srcTm->UnadviseSink(m_dwUIElementSinkCookie);
			m_dwUIElementSinkCookie = TF_INVALID_COOKIE;
		}
		srcTm->Release();
	}
}

// Returns open mode compartments and compartment manager.
// Function fails if it fails to acquire any of the objects to be returned.
BOOL CTsfUiLessMode::GetCompartments( ITfCompartmentMgr** ppcm, ITfCompartment** ppTfOpenMode, ITfCompartment** ppTfConvMode )
{
	ITfCompartmentMgr* pcm = NULL;
	ITfCompartment* pTfOpenMode = NULL;
	ITfCompartment* pTfConvMode = NULL;

	static GUID _GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION = { 0xCCF05DD8, 0x4A87, 0x11D7, 0xA6, 0xE2, 0x00, 0x06, 0x5B, 0x84, 0x43, 0x5C };

	HRESULT hr;
	if (SUCCEEDED(hr = m_tm->QueryInterface( IID_ITfCompartmentMgr, (void**)&pcm )))
	{
		if (SUCCEEDED(hr = pcm->GetCompartment( GUID_COMPARTMENT_KEYBOARD_OPENCLOSE, &pTfOpenMode )))
		{
			if (SUCCEEDED(hr = pcm->GetCompartment( _GUID_COMPARTMENT_KEYBOARD_INPUTMODE_CONVERSION, &pTfConvMode )))
			{
				*ppcm = pcm;
				*ppTfOpenMode = pTfOpenMode;
				*ppTfConvMode = pTfConvMode;
				return TRUE;
			}
			pTfOpenMode->Release();
		}
		pcm->Release();
	}
	return FALSE;
}

// There are three ways to call this function:
// SetupCompartmentSinks() : initialization
// SetupCompartmentSinks(FALSE, openmode, convmode) : Resetting sinks. This is necessary as DaYi and Array IME resets compartment on switching input locale
// SetupCompartmentSinks(TRUE) : clean up sinks
BOOL CTsfUiLessMode::SetupCompartmentSinks( BOOL bRemoveOnly, ITfCompartment* pTfOpenMode, ITfCompartment* pTfConvMode )
{
	bool bLocalCompartments = false;
	ITfCompartmentMgr* pcm = NULL;
	BOOL bRc = FALSE;
	HRESULT hr = E_FAIL;

	if ( !pTfOpenMode && !pTfConvMode )
	{
		bLocalCompartments = true;
		GetCompartments( &pcm, &pTfOpenMode, &pTfConvMode );
	}
	if ( !( pTfOpenMode && pTfConvMode ) )
	{
		// Invalid parameters or GetCompartments() has failed.
		return FALSE;
	}
	ITfSource *srcOpenMode = NULL;
	if (SUCCEEDED(hr = pTfOpenMode->QueryInterface( IID_ITfSource, (void**)&srcOpenMode )))
	{
		// Remove existing sink for open mode
		if ( m_dwOpenModeSinkCookie != TF_INVALID_COOKIE )
		{
			srcOpenMode->UnadviseSink( m_dwOpenModeSinkCookie );
			m_dwOpenModeSinkCookie = TF_INVALID_COOKIE;
		}
		// Setup sink for open mode (toggle state) change
		if ( bRemoveOnly || SUCCEEDED(hr = srcOpenMode->AdviseSink( IID_ITfCompartmentEventSink, (ITfCompartmentEventSink*)m_TsfSink, &m_dwOpenModeSinkCookie )))
		{
			ITfSource *srcConvMode = NULL;
			if (SUCCEEDED(hr = pTfConvMode->QueryInterface( IID_ITfSource, (void**)&srcConvMode )))
			{
				// Remove existing sink for open mode
				if ( m_dwConvModeSinkCookie != TF_INVALID_COOKIE )
				{
					srcConvMode->UnadviseSink( m_dwConvModeSinkCookie );
					m_dwConvModeSinkCookie = TF_INVALID_COOKIE;
				}
				// Setup sink for open mode (toggle state) change
				if ( bRemoveOnly || SUCCEEDED(hr = srcConvMode->AdviseSink( IID_ITfCompartmentEventSink, (ITfCompartmentEventSink*)m_TsfSink, &m_dwConvModeSinkCookie )))
				{
					bRc = TRUE;
				}
				srcConvMode->Release();
			}
		}
		srcOpenMode->Release();
	}
	if ( bLocalCompartments )
	{
		pTfOpenMode->Release();
		pTfConvMode->Release();
		pcm->Release();
	}
	return bRc;
}


WORD ImeUi_GetPrimaryLanguage()
{	
    return GETPRIMLANG(); 
};
    
DWORD ImeUi_GetImeId(UINT uIndex)
{
    return GetImeId(uIndex); 
};

WORD ImeUi_GetLanguage()
{ 
    return GETLANG(); 
};

PTSTR ImeUi_GetIndicatior()
{
	return g_pszIndicatior ;
};


bool ImeUi_IsShowReadingWindow()
{
	return g_bReadingWindow;
};

bool ImeUi_IsShowCandListWindow()
{
	return g_bCandList;
};

bool ImeUi_IsVerticalCand()
{
	return g_bVerticalCand;
};

bool ImeUi_IsHorizontalReading()
{
	return g_bHorizontalReading;
};

TCHAR* ImeUi_GetCandidate(UINT idx)
{
	if ( idx < MAX_CANDLIST)
		return g_szCandidate[idx];
	else
		return g_szCandidate[0];
}

DWORD ImeUi_GetCandidateSelection()
{
	return g_dwSelection;
}

DWORD ImeUi_GetCandidateCount()
{
	return g_dwCount;
}

TCHAR* ImeUi_GetCompositionString()
{
	return g_szCompositionString;
}

BYTE* ImeUi_GetCompStringAttr()
{
	return  g_szCompAttrString;
}

DWORD ImeUi_GetImeCursorChars()
{
	return g_IMECursorChars;
}

